Merge "Add NR SA OVERRIDE_NETWORK_TYPE_NR_ADVANCED for TelephonyDisplayInfo" into sc-dev
diff --git a/Android.bp b/Android.bp
index 9655daf..7e68986 100644
--- a/Android.bp
+++ b/Android.bp
@@ -369,6 +369,7 @@
":framework_native_aidl",
":gatekeeper_aidl",
":gsiservice_aidl",
+ ":idmap2_core_aidl",
":incidentcompanion_aidl",
":inputconstants_aidl",
":installd_aidl",
@@ -405,6 +406,7 @@
":framework-statsd-sources",
":framework-tethering-srcs",
":framework-wifi-updatable-sources",
+ ":ike-srcs",
":updatable-media-srcs",
],
visibility: ["//visibility:private"],
@@ -413,6 +415,7 @@
java_library {
name: "framework-updatable-stubs-module_libs_api",
static_libs: [
+ "android.net.ipsec.ike.stubs.module_lib",
"framework-appsearch.stubs.module_lib",
"framework-graphics.stubs.module_lib",
"framework-media.stubs.module_lib",
@@ -432,6 +435,7 @@
name: "framework-all",
installable: false,
static_libs: [
+ "android.net.ipsec.ike.impl",
"framework-minus-apex",
"framework-appsearch.impl",
"framework-graphics.impl",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 86364af..3f2e898 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -306,6 +306,7 @@
name: "android_stubs_current",
srcs: [ ":api-stubs-docs-non-updatable" ],
static_libs: [
+ "android.net.ipsec.ike.stubs",
"art.module.public.api.stubs",
"conscrypt.module.public.api.stubs",
"framework-appsearch.stubs",
@@ -328,6 +329,7 @@
name: "android_system_stubs_current",
srcs: [ ":system-api-stubs-docs-non-updatable" ],
static_libs: [
+ "android.net.ipsec.ike.stubs.system",
"art.module.public.api.stubs",
"conscrypt.module.public.api.stubs",
"framework-appsearch.stubs.system",
@@ -366,6 +368,7 @@
static_libs: [
// Modules do not have test APIs, but we want to include their SystemApis, like we include
// the SystemApi of framework-non-updatable-sources.
+ "android.net.ipsec.ike.stubs.system",
"art.module.public.api.stubs",
"conscrypt.module.public.api.stubs",
"framework-appsearch.stubs.system",
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index a3b8013..905000a 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -208,8 +208,8 @@
ctor public GetByUriRequest.Builder();
method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.lang.String...);
method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
- method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.lang.String...);
- method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.lang.String...);
+ method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.GetByUriRequest build();
method @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String);
}
@@ -243,8 +243,8 @@
public static final class RemoveByUriRequest.Builder {
ctor public RemoveByUriRequest.Builder();
- method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUri(@NonNull java.lang.String...);
- method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUri(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.lang.String...);
+ method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.RemoveByUriRequest build();
method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String);
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
index 0fcf061..656608d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -122,14 +122,14 @@
/** Adds one or more URIs to the request. */
@NonNull
- public Builder addUri(@NonNull String... uris) {
+ public Builder addUris(@NonNull String... uris) {
Preconditions.checkNotNull(uris);
- return addUri(Arrays.asList(uris));
+ return addUris(Arrays.asList(uris));
}
/** Adds one or more URIs to the request. */
@NonNull
- public Builder addUri(@NonNull Collection<String> uris) {
+ public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(uris);
mUris.addAll(uris);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
index 2104198d..198eee8 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -73,14 +73,14 @@
/** Adds one or more URIs to the request. */
@NonNull
- public Builder addUri(@NonNull String... uris) {
+ public Builder addUris(@NonNull String... uris) {
Preconditions.checkNotNull(uris);
- return addUri(Arrays.asList(uris));
+ return addUris(Arrays.asList(uris));
}
/** Adds one or more URIs to the request. */
@NonNull
- public Builder addUri(@NonNull Collection<String> uris) {
+ public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(uris);
mUris.addAll(uris);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index 0328d54..4869aa3 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -163,7 +163,7 @@
/** Adds deletedTypes to the list of deleted schema types. */
@NonNull
- public Builder addDeletedType(@NonNull Collection<String> deletedTypes) {
+ public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mDeletedTypes.addAll(Preconditions.checkNotNull(deletedTypes));
return this;
@@ -171,7 +171,7 @@
/** Adds incompatibleTypes to the list of incompatible schema types. */
@NonNull
- public Builder addIncompatibleType(@NonNull Collection<String> incompatibleTypes) {
+ public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mIncompatibleTypes.addAll(Preconditions.checkNotNull(incompatibleTypes));
return this;
@@ -179,7 +179,7 @@
/** Adds migratedTypes to the list of migrated schema types. */
@NonNull
- public Builder addMigratedType(@NonNull Collection<String> migratedTypes) {
+ public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
return this;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java
index 1b4d284..14dd472 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java
@@ -147,35 +147,41 @@
if (bundle == null) {
return 0;
}
- int[] hashCodes = new int[bundle.size()];
- int i = 0;
+ int[] hashCodes = new int[bundle.size() + 1];
+ int hashCodeIdx = 0;
// Bundle inherit its hashCode() from Object.java, which only relative to their memory
// address. Bundle doesn't have an order, so we should iterate all keys and combine
// their value's hashcode into an array. And use the hashcode of the array to be
// the hashcode of the bundle.
- for (String key : bundle.keySet()) {
- Object value = bundle.get(key);
+ // Because bundle.keySet() doesn't guarantee any particular order, we need to sort the keys
+ // in case the iteration order varies from run to run.
+ String[] keys = bundle.keySet().toArray(new String[0]);
+ Arrays.sort(keys);
+ // Hash the keys so we can detect key-only differences
+ hashCodes[hashCodeIdx++] = Arrays.hashCode(keys);
+ for (int keyIdx = 0; keyIdx < keys.length; keyIdx++) {
+ Object value = bundle.get(keys[keyIdx]);
if (value instanceof Bundle) {
- hashCodes[i++] = deepHashCode((Bundle) value);
+ hashCodes[hashCodeIdx++] = deepHashCode((Bundle) value);
} else if (value instanceof int[]) {
- hashCodes[i++] = Arrays.hashCode((int[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((int[]) value);
} else if (value instanceof byte[]) {
- hashCodes[i++] = Arrays.hashCode((byte[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((byte[]) value);
} else if (value instanceof char[]) {
- hashCodes[i++] = Arrays.hashCode((char[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((char[]) value);
} else if (value instanceof long[]) {
- hashCodes[i++] = Arrays.hashCode((long[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((long[]) value);
} else if (value instanceof float[]) {
- hashCodes[i++] = Arrays.hashCode((float[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((float[]) value);
} else if (value instanceof short[]) {
- hashCodes[i++] = Arrays.hashCode((short[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((short[]) value);
} else if (value instanceof double[]) {
- hashCodes[i++] = Arrays.hashCode((double[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((double[]) value);
} else if (value instanceof boolean[]) {
- hashCodes[i++] = Arrays.hashCode((boolean[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((boolean[]) value);
} else if (value instanceof String[]) {
// Optimization to avoid Object[] handler creating an inner array for common cases
- hashCodes[i++] = Arrays.hashCode((String[]) value);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode((String[]) value);
} else if (value instanceof Object[]) {
Object[] array = (Object[]) value;
int[] innerHashCodes = new int[array.length];
@@ -186,7 +192,7 @@
innerHashCodes[j] = array[j].hashCode();
}
}
- hashCodes[i++] = Arrays.hashCode(innerHashCodes);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
} else if (value instanceof ArrayList) {
ArrayList<?> list = (ArrayList<?>) value;
int[] innerHashCodes = new int[list.size()];
@@ -198,7 +204,7 @@
innerHashCodes[j] = item.hashCode();
}
}
- hashCodes[i++] = Arrays.hashCode(innerHashCodes);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
} else if (value instanceof SparseArray) {
SparseArray<?> array = (SparseArray<?>) value;
int[] innerHashCodes = new int[array.size() * 2];
@@ -211,9 +217,9 @@
innerHashCodes[j * 2 + 1] = item.hashCode();
}
}
- hashCodes[i++] = Arrays.hashCode(innerHashCodes);
+ hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes);
} else {
- hashCodes[i++] = value.hashCode();
+ hashCodes[hashCodeIdx++] = value.hashCode();
}
}
return Arrays.hashCode(hashCodes);
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..c369801 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -108,7 +108,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);
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/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 12699b7..6ba5572 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-Ibe06fb9c574c8718191f833bb042fa10c300e4e2
+I2bf8bd9db1b71b7da4ab50dd7480e4529678413a
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/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
index 20fb909..44d5180 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
@@ -59,7 +59,7 @@
session.getByUri(
new GetByUriRequest.Builder()
.setNamespace(namespace)
- .addUri(uris)
+ .addUris(uris)
.build()));
assertThat(result.getSuccesses()).hasSize(uris.length);
assertThat(result.getFailures()).isEmpty();
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 bfc153f..71fe55f 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:");
}
@@ -1990,8 +1995,11 @@
}
final boolean shouldForceBatchJob;
- // Restricted jobs must always be batched
- if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
+ if (job.shouldTreatAsExpeditedJob()) {
+ // Never batch expedited jobs, even for RESTRICTED apps.
+ shouldForceBatchJob = false;
+ } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
+ // Restricted jobs must always be batched
shouldForceBatchJob = true;
} else if (job.getNumFailures() > 0) {
shouldForceBatchJob = false;
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/api/Android.bp b/api/Android.bp
index 69dce97..d5c6bf6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -28,6 +28,7 @@
genrule {
name: "frameworks-base-api-current.txt",
srcs: [
+ ":android.net.ipsec.ike{.public.api.txt}",
":art.module.public.api{.public.api.txt}",
":conscrypt.module.public.api{.public.api.txt}",
":framework-appsearch{.public.api.txt}",
@@ -64,6 +65,7 @@
genrule {
name: "frameworks-base-api-current.srcjar",
srcs: [
+ ":android.net.ipsec.ike{.public.stubs.source}",
":api-stubs-docs-non-updatable",
":art.module.public.api{.public.stubs.source}",
":conscrypt.module.public.api{.public.stubs.source}",
@@ -88,6 +90,7 @@
genrule {
name: "frameworks-base-api-removed.txt",
srcs: [
+ ":android.net.ipsec.ike{.public.removed-api.txt}",
":art.module.public.api{.public.removed-api.txt}",
":conscrypt.module.public.api{.public.removed-api.txt}",
":framework-appsearch{.public.removed-api.txt}",
@@ -123,6 +126,7 @@
genrule {
name: "frameworks-base-api-system-current.txt",
srcs: [
+ ":android.net.ipsec.ike{.system.api.txt}",
":framework-appsearch{.system.api.txt}",
":framework-graphics{.system.api.txt}",
":framework-media{.system.api.txt}",
@@ -156,6 +160,7 @@
genrule {
name: "frameworks-base-api-system-removed.txt",
srcs: [
+ ":android.net.ipsec.ike{.system.removed-api.txt}",
":framework-appsearch{.system.removed-api.txt}",
":framework-graphics{.system.removed-api.txt}",
":framework-media{.system.removed-api.txt}",
@@ -189,6 +194,7 @@
genrule {
name: "frameworks-base-api-module-lib-current.txt",
srcs: [
+ ":android.net.ipsec.ike{.module-lib.api.txt}",
":framework-appsearch{.module-lib.api.txt}",
":framework-graphics{.module-lib.api.txt}",
":framework-media{.module-lib.api.txt}",
@@ -221,6 +227,7 @@
genrule {
name: "frameworks-base-api-module-lib-removed.txt",
srcs: [
+ ":android.net.ipsec.ike{.module-lib.removed-api.txt}",
":framework-appsearch{.module-lib.removed-api.txt}",
":framework-graphics{.module-lib.removed-api.txt}",
":framework-media{.module-lib.removed-api.txt}",
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index e21a6b2..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,23 +148,27 @@
"tests/ResourceUtilsTests.cpp",
"tests/ResultTests.cpp",
"tests/XmlParserTests.cpp",
- "tests/ZipFileTests.cpp",
],
required: [
"idmap2",
],
- static_libs: ["libgmock"],
+ 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: {
@@ -155,17 +177,28 @@
"libbase",
"libcutils",
"libidmap2",
+ "libidmap2_policies",
"liblog",
+ "libprotobuf-cpp-lite",
"libutils",
"libziparchive",
- "libidmap2_policies",
],
shared_libs: [
"libz",
],
+ data: [
+ ":libz",
+ ":idmap2",
+ ],
},
},
- data: ["tests/data/**/*.apk"],
+ data: [
+ "tests/data/**/*.apk",
+ ],
+ compile_multilib: "first",
+ test_options: {
+ unit_test: true,
+ },
}
cc_binary {
@@ -182,6 +215,9 @@
"idmap2/Lookup.cpp",
"idmap2/Main.cpp",
],
+ static_libs: [
+ "libidmap2_protos",
+ ],
target: {
android: {
shared_libs: [
@@ -189,9 +225,11 @@
"libbase",
"libcutils",
"libidmap2",
- "libutils",
- "libziparchive",
"libidmap2_policies",
+ "libprotobuf-cpp-lite",
+ "libutils",
+ "libz",
+ "libziparchive",
],
},
host: {
@@ -200,10 +238,11 @@
"libbase",
"libcutils",
"libidmap2",
+ "libidmap2_policies",
"liblog",
+ "libprotobuf-cpp-lite",
"libutils",
"libziparchive",
- "libidmap2_policies",
],
shared_libs: [
"libz",
@@ -229,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"],
@@ -241,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 {
@@ -274,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/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml
deleted file mode 100644
index 5147f4e..0000000
--- a/cmds/idmap2/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Config for idmap2_tests">
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" />
- </target_preparer>
- <option name="test-suite-tag" value="idmap2_tests" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="idmap2_tests" />
- </test>
-</configuration>
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/tools/hiddenapi/Android.bp b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
similarity index 64%
copy from tools/hiddenapi/Android.bp
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
index e0eb06cb..6375d24 100644
--- a/tools/hiddenapi/Android.bp
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-python_binary_host {
- name: "merge_csv",
- main: "merge_csv.py",
- srcs: ["merge_csv.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: true
- },
- },
-}
+package android.os;
+
+/**
+ * @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/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
new file mode 100644
index 0000000..f67d8be
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.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/tools/hiddenapi/Android.bp b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index e0eb06cb..6c2af27 100644
--- a/tools/hiddenapi/Android.bp
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,17 +14,13 @@
* limitations under the License.
*/
-python_binary_host {
- name: "merge_csv",
- main: "merge_csv.py",
- srcs: ["merge_csv.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: true
- },
- },
-}
+package android.os;
+
+/**
+ * @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/core/api/current.txt b/core/api/current.txt
index 19c8710..c72608f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -435,6 +435,7 @@
field public static final int clickable = 16842981; // 0x10100e5
field public static final int clipChildren = 16842986; // 0x10100ea
field public static final int clipOrientation = 16843274; // 0x101020a
+ field public static final int clipToOutline = 16844328; // 0x1010628
field public static final int clipToPadding = 16842987; // 0x10100eb
field public static final int closeIcon = 16843905; // 0x1010481
field @Deprecated public static final int codes = 16843330; // 0x1010242
@@ -572,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
@@ -847,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
@@ -2936,12 +2939,12 @@
field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
- field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b
field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19
field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15
+ field public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43; // 0x2b
field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
@@ -5554,15 +5557,19 @@
field public static final int DEFAULT_LIGHTS = 4; // 0x4
field public static final int DEFAULT_SOUND = 1; // 0x1
field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final String EXTRA_CALL_PERSON = "android.callPerson";
field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
field public static final String EXTRA_COLORIZED = "android.colorized";
field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+ field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
field public static final String EXTRA_INFO_TEXT = "android.infoText";
field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
@@ -5594,6 +5601,8 @@
field public static final String EXTRA_TEXT_LINES = "android.textLines";
field public static final String EXTRA_TITLE = "android.title";
field public static final String EXTRA_TITLE_BIG = "android.title.big";
+ field public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+ field public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
field public static final int FLAG_BUBBLE = 4096; // 0x1000
field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
@@ -5842,6 +5851,14 @@
method @NonNull public android.app.Notification.Builder setWhen(long);
}
+ public static class Notification.CallStyle extends android.app.Notification.Style {
+ method @NonNull public static android.app.Notification.CallStyle forIncomingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+ method @NonNull public static android.app.Notification.CallStyle forOngoingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent);
+ method @NonNull public static android.app.Notification.CallStyle forScreeningCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+ method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence);
+ }
+
public static final class Notification.CarExtender implements android.app.Notification.Extender {
ctor public Notification.CarExtender();
ctor public Notification.CarExtender(android.app.Notification);
@@ -10964,6 +10981,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";
@@ -23017,10 +23035,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);
@@ -30594,6 +30614,7 @@
field public static String DIRECTORY_NOTIFICATIONS;
field public static String DIRECTORY_PICTURES;
field public static String DIRECTORY_PODCASTS;
+ field @NonNull public static String DIRECTORY_RECORDINGS;
field public static String DIRECTORY_RINGTONES;
field public static String DIRECTORY_SCREENSHOTS;
field public static final String MEDIA_BAD_REMOVAL = "bad_removal";
@@ -40338,6 +40359,7 @@
field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
+ field public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL = "store_sim_pin_for_unattended_reboot_bool";
field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
field public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL = "support_add_conference_participants_bool";
@@ -42064,7 +42086,7 @@
method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
- method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState();
method @Nullable public android.telephony.SignalStrength getSignalStrength();
method public int getSimCarrierId();
method @Nullable public CharSequence getSimCarrierIdName();
@@ -42923,6 +42945,16 @@
field public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; // 0x2
}
+ public final class ImsRegistrationAttributes implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAttributeFlags();
+ method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
+ method public int getTransportType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1; // 0x1
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsRegistrationAttributes> CREATOR;
+ }
+
public class RcsUceAdapter {
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException;
}
@@ -42932,7 +42964,6 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
- field public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1; // 0x1
field public static final int REGISTRATION_STATE_NOT_REGISTERED = 0; // 0x0
field public static final int REGISTRATION_STATE_REGISTERED = 2; // 0x2
field public static final int REGISTRATION_STATE_REGISTERING = 1; // 0x1
@@ -42941,9 +42972,9 @@
public static class RegistrationManager.RegistrationCallback {
ctor public RegistrationManager.RegistrationCallback();
method @Deprecated public void onRegistered(int);
- method public void onRegistered(int, int);
+ method public void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
method @Deprecated public void onRegistering(int);
- method public void onRegistering(int, int);
+ method public void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes);
method public void onTechnologyChangeFailed(int, @NonNull android.telephony.ims.ImsReasonInfo);
method public void onUnregistered(@NonNull android.telephony.ims.ImsReasonInfo);
}
@@ -50688,6 +50719,7 @@
method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
+ field public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET = "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET";
field public static final String EXTRA_CLIENT_STATE = "android.view.autofill.extra.CLIENT_STATE";
}
@@ -53560,6 +53592,7 @@
method @Nullable public android.graphics.BlendMode getBlendMode();
method @ColorInt public int getColor();
method public int getMaxHeight();
+ method public int getType();
method public boolean isFinished();
method public void onAbsorb(int);
method public void onPull(float);
@@ -53568,7 +53601,10 @@
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 {
@@ -54635,6 +54671,8 @@
method public void setTextViewText(@IdRes int, CharSequence);
method public void setTextViewTextSize(@IdRes int, int, float);
method public void setUri(@IdRes int, String, android.net.Uri);
+ method public void setViewOutlinePreferredRadius(@IdRes int, float, int);
+ method public void setViewOutlinePreferredRadiusDimen(@IdRes int, @DimenRes int);
method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int);
method public void setViewVisibility(@IdRes int, int);
method public void showNext(@IdRes int);
@@ -54659,6 +54697,12 @@
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface RemoteViews.RemoteView {
}
+ public static final class RemoteViews.RemoteViewOutlineProvider extends android.view.ViewOutlineProvider {
+ ctor public RemoteViews.RemoteViewOutlineProvider(float);
+ method public void getOutline(@NonNull android.view.View, @NonNull android.graphics.Outline);
+ method public float getRadius();
+ }
+
public abstract class RemoteViewsService extends android.app.Service {
ctor public RemoteViewsService();
method public android.os.IBinder onBind(android.content.Intent);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index bf70803..4e25625 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -162,6 +162,7 @@
}
public class ConnectivityManager {
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5bb3e05..fdd1e66 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 {
@@ -2632,6 +2632,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 +7045,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 +7067,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 {
@@ -11869,6 +11876,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
method public boolean needsOtaServiceProvisioning();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+ method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
@@ -11993,6 +12001,9 @@
field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
+ field public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2; // 0x2
+ field public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1; // 0x1
+ field public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; // 0x0
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
@@ -12974,6 +12985,16 @@
field public static final String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service";
}
+ public final class ImsRegistrationAttributes implements android.os.Parcelable {
+ method public int getRegistrationTechnology();
+ }
+
+ public static final class ImsRegistrationAttributes.Builder {
+ ctor public ImsRegistrationAttributes.Builder(int);
+ method @NonNull public android.telephony.ims.ImsRegistrationAttributes build();
+ method @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setFeatureTags(@NonNull java.util.Set<java.lang.String>);
+ }
+
public class ImsService extends android.app.Service {
ctor public ImsService();
method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int);
@@ -13757,7 +13778,9 @@
ctor public ImsRegistrationImplBase();
method public final void onDeregistered(android.telephony.ims.ImsReasonInfo);
method public final void onRegistered(int);
+ method public final void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
method public final void onRegistering(int);
+ method public final void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes);
method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]);
method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo);
method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e39b2b8..0611031 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
}
}
@@ -472,6 +472,7 @@
method @NonNull public String getOwnerName();
method @Nullable public String getTimeZone();
method public boolean isLeaveAllSystemAppsEnabled();
+ method public void logParams(@NonNull String);
method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR;
}
@@ -495,6 +496,7 @@
method public boolean isKeepAccountMigrated();
method public boolean isLeaveAllSystemAppsEnabled();
method public boolean isOrganizationOwnedProvisioning();
+ method public void logParams(@NonNull String);
method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR;
}
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 4b2d741..768ec38 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -20,12 +20,12 @@
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
@@ -97,10 +97,10 @@
GESTURE_UNKNOWN,
GESTURE_TOUCH_EXPLORATION,
GESTURE_2_FINGER_SINGLE_TAP,
- GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD,
GESTURE_2_FINGER_DOUBLE_TAP,
GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
GESTURE_2_FINGER_TRIPLE_TAP,
+ GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD,
GESTURE_3_FINGER_SINGLE_TAP,
GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
GESTURE_3_FINGER_DOUBLE_TAP,
@@ -232,8 +232,8 @@
case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH";
case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION";
case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
- case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD:
- return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD";
+ case GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD:
+ return "GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD";
case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 1fa7fa2..dab4a5d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -445,8 +445,8 @@
/** The user has performed a three-finger double tap and hold gesture on the touch screen. */
public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
- /** The user has performed a two-finger single-tap and hold gesture on the touch screen. */
- public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43;
+ /** The user has performed a two-finger triple-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43;
/** The user has performed a three-finger single-tap and hold gesture on the touch screen. */
public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44;
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/GameManager.java b/core/java/android/app/GameManager.java
index 8b6570f..ac1fa1e 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -16,7 +16,9 @@
package android.app;
+import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
import android.content.Context;
@@ -73,8 +75,8 @@
/**
* Returns the game mode for the given package.
*/
- // TODO(b/178111358): Add @RequiresPermission.
@UserHandleAware
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public @GameMode int getGameMode(String packageName) {
try {
return mService.getGameMode(packageName, mContext.getUserId());
@@ -86,8 +88,8 @@
/**
* Sets the game mode for the given package.
*/
- // TODO(b/178111358): Add @RequiresPermission.
@UserHandleAware
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public void setGameMode(String packageName, @GameMode int gameMode) {
try {
mService.setGameMode(packageName, gameMode, mContext.getUserId());
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 49f508d..050f34a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,7 +22,10 @@
import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.ColorInt;
+import android.annotation.ColorRes;
import android.annotation.DimenRes;
import android.annotation.Dimension;
import android.annotation.DrawableRes;
@@ -33,6 +36,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -403,6 +407,7 @@
STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
STANDARD_LAYOUTS.add(R.layout.notification_template_header);
}
@@ -649,7 +654,7 @@
private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
- MessagingStyle.class);
+ MessagingStyle.class, CallStyle.class);
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
@@ -1318,6 +1323,53 @@
public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
/**
+ * {@link #extras} key: the type of call represented by the
+ * {@link android.app.Notification.CallStyle} notification. This extra is an int.
+ * @hide
+ */
+ public static final String EXTRA_CALL_TYPE = "android.callType";
+
+ /**
+ * {@link #extras} key: the person to be displayed as calling for the
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
+ */
+ public static final String EXTRA_CALL_PERSON = "android.callPerson";
+
+ /**
+ * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
+ * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
+ */
+ public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+
+ /**
+ * {@link #extras} key: the text to be displayed as a verification status of the caller on a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link CharSequence}.
+ */
+ public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
+
+ /**
+ * {@link #extras} key: the intent to be sent when the users answers a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link PendingIntent}.
+ */
+ public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
+
+ /**
+ * {@link #extras} key: the intent to be sent when the users declines a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link PendingIntent}.
+ */
+ public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+
+ /**
+ * {@link #extras} key: the intent to be sent when the users hangs up a
+ * {@link android.app.Notification.CallStyle} notification. This extra is a
+ * {@link PendingIntent}.
+ */
+ public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
+
+ /**
* {@link #extras} key: whether the notification should be colorized as
* supplied to {@link Builder#setColorized(boolean)}.
*/
@@ -5876,11 +5928,11 @@
return summary;
}
- private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
+ private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
StandardTemplateParams p) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
- emphazisedMode ? getEmphasizedActionLayoutResource()
+ emphasizedMode ? getEmphasizedActionLayoutResource()
: tombstone ? getActionTombstoneLayoutResource()
: getActionLayoutResource());
if (!tombstone) {
@@ -5890,43 +5942,42 @@
if (action.mRemoteInputs != null) {
button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
}
- if (emphazisedMode) {
+ if (emphasizedMode) {
// change the background bgColor
CharSequence title = action.title;
- ColorStateList[] outResultColor = null;
+ ColorStateList[] outResultColor = new ColorStateList[1];
int background = resolveBackgroundColor(p);
if (isLegacy()) {
title = ContrastColorUtil.clearColorSpans(title);
} else {
- outResultColor = new ColorStateList[1];
title = ensureColorSpanContrast(title, background, outResultColor);
}
button.setTextViewText(R.id.action0, processTextSpans(title));
- setTextViewColorPrimary(button, R.id.action0, p);
- int rippleColor;
- boolean hasColorOverride = outResultColor != null && outResultColor[0] != null;
+ int textColor = getPrimaryTextColor(p);
+ boolean hasColorOverride = outResultColor[0] != null;
if (hasColorOverride) {
// There's a span spanning the full text, let's take it and use it as the
// background color
background = outResultColor[0].getDefaultColor();
- int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+ textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
background, mInNightMode);
- button.setTextColor(R.id.action0, textColor);
- rippleColor = textColor;
} else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p)
&& mTintActionButtons && !mInNightMode) {
- rippleColor = resolveContrastColor(p);
- button.setTextColor(R.id.action0, rippleColor);
- } else {
- rippleColor = getPrimaryTextColor(p);
+ textColor = resolveContrastColor(p);
}
+ button.setTextColor(R.id.action0, textColor);
// We only want about 20% alpha for the ripple
- rippleColor = (rippleColor & 0x00ffffff) | 0x33000000;
+ final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
button.setColorStateList(R.id.action0, "setRippleColor",
ColorStateList.valueOf(rippleColor));
button.setColorStateList(R.id.action0, "setButtonBackground",
ColorStateList.valueOf(background));
button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride);
+ if (p.mAllowActionIcons) {
+ button.setImageViewIcon(R.id.action0, action.getIcon());
+ boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
+ button.setBoolean(R.id.action0, "setWrapModePriority", priority);
+ }
} else {
button.setTextViewText(R.id.action0, processTextSpans(
processLegacyText(action.title)));
@@ -5936,8 +5987,12 @@
button.setTextColor(R.id.action0, resolveContrastColor(p));
}
}
- button.setIntTag(R.id.action0, R.id.notification_action_index_tag,
- mActions.indexOf(action));
+ // CallStyle notifications add action buttons which don't actually exist in mActions,
+ // so we have to omit the index in that case.
+ int actionIndex = mActions.indexOf(action);
+ if (actionIndex != -1) {
+ button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
+ }
return button;
}
@@ -6371,6 +6426,10 @@
return R.layout.notification_template_material_conversation;
}
+ private int getCallLayoutResource() {
+ return R.layout.notification_template_material_call;
+ }
+
private int getActionLayoutResource() {
return R.layout.notification_material_action;
}
@@ -8039,6 +8098,10 @@
: mBuilder.getMessagingLayoutResource(),
p,
bindResult);
+ if (isConversationLayout) {
+ mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+ mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+ }
addExtras(mBuilder.mN.extras);
if (!isConversationLayout) {
@@ -8925,6 +8988,441 @@
}
}
+
+
+ /**
+ * Helper class for generating large-format notifications that include a large image attachment.
+ *
+ * Here's how you'd set the <code>CallStyle</code> on a notification:
+ * <pre class="prettyprint">
+ * Notification notif = new Notification.Builder(mContext)
+ * .setSmallIcon(R.drawable.new_post)
+ * .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
+ * .build();
+ * </pre>
+ */
+ public static class CallStyle extends Style {
+ private static final int CALL_TYPE_INCOMING = 1;
+ private static final int CALL_TYPE_ONGOING = 2;
+ private static final int CALL_TYPE_SCREENING = 3;
+
+ /**
+ * This is a key used privately on the action.extras to give spacing priority
+ * to the required call actions
+ */
+ private static final String KEY_ACTION_PRIORITY = "key_action_priority";
+
+ private int mCallType;
+ private Person mPerson;
+ private PendingIntent mAnswerIntent;
+ private PendingIntent mDeclineIntent;
+ private PendingIntent mHangUpIntent;
+ private Icon mVerificationIcon;
+ private CharSequence mVerificationText;
+
+ CallStyle() {
+ }
+
+ /**
+ * Create a CallStyle for an incoming call.
+ * This notification will have a decline and an answer action, will allow a single
+ * custom {@link Builder#addAction(Action) action}, and will have a default
+ * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
+ *
+ * @param person The person displayed as the caller.
+ * The person also needs to have a non-empty name associated with it.
+ * @param declineIntent The intent to be sent when the user taps the decline action
+ * @param answerIntent The intent to be sent when the user taps the answer action
+ */
+ @NonNull
+ public static CallStyle forIncomingCall(@NonNull Person person,
+ @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
+ return new CallStyle(CALL_TYPE_INCOMING, person,
+ null /* hangUpIntent */,
+ requireNonNull(declineIntent, "declineIntent is required"),
+ requireNonNull(answerIntent, "answerIntent is required")
+ );
+ }
+
+ /**
+ * Create a CallStyle for an ongoing call.
+ * This notification will have a hang up action, will allow up to two
+ * custom {@link Builder#addAction(Action) actions}, and will have a default
+ * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
+ *
+ * @param person The person displayed as being on the other end of the call.
+ * The person also needs to have a non-empty name associated with it.
+ * @param hangUpIntent The intent to be sent when the user taps the hang up action
+ */
+ @NonNull
+ public static CallStyle forOngoingCall(@NonNull Person person,
+ @NonNull PendingIntent hangUpIntent) {
+ return new CallStyle(CALL_TYPE_ONGOING, person,
+ requireNonNull(hangUpIntent, "hangUpIntent is required"),
+ null /* declineIntent */,
+ null /* answerIntent */
+ );
+ }
+
+ /**
+ * Create a CallStyle for a call that is being screened.
+ * This notification will have a hang up and an answer action, will allow a single
+ * custom {@link Builder#addAction(Action) action}, and will have a default
+ * {@link Builder#setContentText(CharSequence) content text} for a call that is being
+ * screened.
+ *
+ * @param person The person displayed as the caller.
+ * The person also needs to have a non-empty name associated with it.
+ * @param hangUpIntent The intent to be sent when the user taps the hang up action
+ * @param answerIntent The intent to be sent when the user taps the answer action
+ */
+ @NonNull
+ public static CallStyle forScreeningCall(@NonNull Person person,
+ @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
+ return new CallStyle(CALL_TYPE_SCREENING, person,
+ requireNonNull(hangUpIntent, "hangUpIntent is required"),
+ null /* declineIntent */,
+ requireNonNull(answerIntent, "answerIntent is required")
+ );
+ }
+
+ /**
+ * @param person The person displayed for the incoming call.
+ * The user also needs to have a non-empty name associated with it.
+ * @param hangUpIntent The intent to be sent when the user taps the hang up action
+ * @param declineIntent The intent to be sent when the user taps the decline action
+ * @param answerIntent The intent to be sent when the user taps the answer action
+ */
+ private CallStyle(int callType, @NonNull Person person,
+ @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
+ @Nullable PendingIntent answerIntent) {
+ if (person == null || TextUtils.isEmpty(person.getName())) {
+ throw new IllegalArgumentException("person must have a non-empty a name");
+ }
+ mCallType = callType;
+ mPerson = person;
+ mAnswerIntent = answerIntent;
+ mDeclineIntent = declineIntent;
+ mHangUpIntent = hangUpIntent;
+ }
+
+ /**
+ * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
+ * as a verification status of the caller.
+ */
+ @NonNull
+ public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
+ mVerificationIcon = verificationIcon;
+ return this;
+ }
+
+ /**
+ * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
+ * as a verification status of the caller.
+ */
+ @NonNull
+ public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
+ mVerificationText = safeCharSequence(verificationText);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean displayCustomViewInline() {
+ // This is a lie; True is returned to make sure that the custom view is not used
+ // instead of the template, but it will not actually be included.
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void purgeResources() {
+ super.purgeResources();
+ if (mVerificationIcon != null) {
+ mVerificationIcon.convertToAshmem();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void reduceImageSizes(Context context) {
+ super.reduceImageSizes(context);
+ if (mVerificationIcon != null) {
+ int rightIconSize = context.getResources().getDimensionPixelSize(
+ ActivityManager.isLowRamDeviceStatic()
+ ? R.dimen.notification_right_icon_size_low_ram
+ : R.dimen.notification_right_icon_size);
+ mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeContentView(boolean increasedHeight) {
+ return makeCallLayout();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ return makeCallLayout();
+ }
+
+ /**
+ * @hide
+ */
+ public RemoteViews makeBigContentView() {
+ return makeCallLayout();
+ }
+
+ @NonNull
+ private Action makeNegativeAction() {
+ if (mDeclineIntent == null) {
+ return makeAction(R.drawable.ic_call_decline,
+ R.string.call_notification_hang_up_action,
+ R.color.call_notification_decline_color, mHangUpIntent);
+ } else {
+ return makeAction(R.drawable.ic_call_decline,
+ R.string.call_notification_decline_action,
+ R.color.call_notification_decline_color, mDeclineIntent);
+ }
+ }
+
+ @Nullable
+ private Action makeAnswerAction() {
+ return mAnswerIntent == null ? null : makeAction(R.drawable.ic_call_answer,
+ R.string.call_notification_answer_action,
+ R.color.call_notification_answer_color, mAnswerIntent);
+ }
+
+ @NonNull
+ private Action makeAction(@DrawableRes int icon, @StringRes int title,
+ @ColorRes int colorRes, PendingIntent intent) {
+ Action action = new Action.Builder(Icon.createWithResource("", icon),
+ new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
+ new ForegroundColorSpan(mBuilder.mContext.getColor(colorRes)),
+ SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
+ intent).build();
+ action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
+ return action;
+ }
+
+ private ArrayList<Action> makeActionsList() {
+ final Action negativeAction = makeNegativeAction();
+ final Action answerAction = makeAnswerAction();
+
+ ArrayList<Action> actions = new ArrayList<>(MAX_ACTION_BUTTONS);
+ final Action lastAction;
+ if (answerAction == null) {
+ // If there's no answer action, put the hang up / decline action at the end
+ lastAction = negativeAction;
+ } else {
+ // Otherwise put the answer action at the end, and put the decline action at start.
+ actions.add(negativeAction);
+ lastAction = answerAction;
+ }
+ // For consistency with the standard actions bar, contextual actions are ignored.
+ for (Action action : Builder.filterOutContextualActions(mBuilder.mActions)) {
+ if (actions.size() >= MAX_ACTION_BUTTONS - 1) {
+ break;
+ }
+ actions.add(action);
+ }
+ actions.add(lastAction);
+ return actions;
+ }
+
+ private RemoteViews makeCallLayout() {
+ Bundle extras = mBuilder.mN.extras;
+ CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
+ if (text == null) {
+ text = getDefaultText();
+ }
+
+ // Bind standard template
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .allowActionIcons(true)
+ .hideLargeIcon(true)
+ .text(text)
+ .summaryText(mBuilder.processLegacyText(mVerificationText));
+ // TODO(b/179178086): hide the snooze button
+ RemoteViews contentView = mBuilder.applyStandardTemplate(
+ mBuilder.getCallLayoutResource(), p, null /* result */);
+
+ // Bind actions.
+ mBuilder.resetStandardTemplateWithActions(contentView);
+ bindCallActions(contentView, p);
+
+ // Bind some extra conversation-specific header fields.
+ mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+ mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+ contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+ bindCallerVerification(contentView, p);
+
+ // Bind some custom CallLayout properties
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.isColorized(p)
+ ? mBuilder.getPrimaryTextColor(p)
+ : mBuilder.resolveContrastColor(p));
+ contentView.setInt(R.id.status_bar_latest_event_content,
+ "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p));
+ contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+ mBuilder.mN.mLargeIcon);
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+ mBuilder.mN.extras);
+
+ return contentView;
+ }
+
+ private void bindCallActions(RemoteViews view, StandardTemplateParams p) {
+ view.setViewVisibility(R.id.actions_container, View.VISIBLE);
+ view.setViewVisibility(R.id.actions, View.VISIBLE);
+ view.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ RemoteViews.MARGIN_BOTTOM, 0);
+
+ // Clear view padding to allow buttons to start on the left edge.
+ // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
+ view.setViewPadding(R.id.actions, 0, 0, 0, 0);
+ // Add an optional indent that will make buttons start at the correct column when
+ // there is enough space to do so (and fall back to the left edge if not).
+ view.setInt(R.id.actions, "setCollapsibleIndentDimen",
+ R.dimen.call_notification_collapsible_indent);
+
+ // Emphasize so that buttons have borders or colored backgrounds
+ boolean emphasizedMode = true;
+ view.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
+ // Use "wrap_content" (unlike normal emphasized mode) and allow prioritizing the
+ // required actions (Answer, Decline, and Hang Up).
+ view.setBoolean(R.id.actions, "setPrioritizedWrapMode", true);
+
+ // Create the buttons for the generated actions list.
+ int i = 0;
+ for (Action action : makeActionsList()) {
+ final RemoteViews button = mBuilder.generateActionButton(action, emphasizedMode, p);
+ if (i > 0) {
+ // Clear start margin from non-first buttons to reduce the gap between buttons.
+ // (8dp remaining gap is from all buttons' standard 4dp inset).
+ button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
+ }
+ view.addView(R.id.actions, button);
+ ++i;
+ }
+ }
+
+ private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
+ if (mVerificationIcon != null) {
+ contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
+ contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
+ mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
+ contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
+ } else {
+ contentView.setViewVisibility(R.id.verification_icon, View.GONE);
+ }
+ if (!TextUtils.isEmpty(mVerificationText)) {
+ contentView.setTextViewText(R.id.verification_text, mVerificationText);
+ mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
+ contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
+ } else {
+ contentView.setViewVisibility(R.id.verification_text, View.GONE);
+ }
+ }
+
+ @Nullable
+ private String getDefaultText() {
+ switch (mCallType) {
+ case CALL_TYPE_INCOMING:
+ return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
+ case CALL_TYPE_ONGOING:
+ return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
+ case CALL_TYPE_SCREENING:
+ return mBuilder.mContext.getString(R.string.call_notification_screening_text);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+ extras.putInt(EXTRA_CALL_TYPE, mCallType);
+ extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
+ if (mVerificationIcon != null) {
+ extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
+ }
+ if (mVerificationText != null) {
+ extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
+ }
+ if (mAnswerIntent != null) {
+ extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
+ }
+ if (mDeclineIntent != null) {
+ extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
+ }
+ if (mHangUpIntent != null) {
+ extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
+ }
+ fixTitleAndTextExtras(extras);
+ }
+
+ private void fixTitleAndTextExtras(Bundle extras) {
+ CharSequence sender = mPerson != null ? mPerson.getName() : null;
+ if (sender != null) {
+ extras.putCharSequence(EXTRA_TITLE, sender);
+ }
+ if (extras.getCharSequence(EXTRA_TEXT) == null) {
+ extras.putCharSequence(EXTRA_TEXT, getDefaultText());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void restoreFromExtras(Bundle extras) {
+ super.restoreFromExtras(extras);
+ mCallType = extras.getInt(EXTRA_CALL_TYPE);
+ mPerson = extras.getParcelable(EXTRA_CALL_PERSON);
+ mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON);
+ mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
+ mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT);
+ mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT);
+ mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean hasSummaryInHeader() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ CallStyle otherS = (CallStyle) other;
+ return !Objects.equals(mCallType, otherS.mCallType)
+ || !Objects.equals(mPerson, otherS.mPerson)
+ || !Objects.equals(mVerificationText, otherS.mVerificationText);
+ }
+ }
+
/**
* Notification style for custom views that are decorated by the system
*
@@ -9474,6 +9972,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>
@@ -9502,6 +10009,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.
*/
@@ -11376,6 +11892,7 @@
boolean mHideActions;
boolean mHideProgress;
boolean mPromotePicture;
+ boolean mAllowActionIcons;
CharSequence title;
CharSequence text;
CharSequence headerTextSecondary;
@@ -11392,6 +11909,7 @@
mHideActions = false;
mHideProgress = false;
mPromotePicture = false;
+ mAllowActionIcons = false;
title = null;
text = null;
summaryText = null;
@@ -11431,6 +11949,11 @@
return this;
}
+ final StandardTemplateParams allowActionIcons(boolean allowActionIcons) {
+ this.mAllowActionIcons = allowActionIcons;
+ return this;
+ }
+
final StandardTemplateParams promotePicture(boolean promotePicture) {
this.mPromotePicture = promotePicture;
return this;
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/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 5e1cbad..9eb9a7b 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -25,6 +25,7 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.stats.devicepolicy.DevicePolicyEnums;
import java.util.Locale;
@@ -35,6 +36,13 @@
*/
@TestApi
public final class FullyManagedDeviceProvisioningParams implements Parcelable {
+ private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
+ "LEAVE_ALL_SYSTEM_APPS_ENABLED";
+ private static final String CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS_PARAM =
+ "CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS";
+ private static final String TIME_ZONE_PROVIDED_PARAM = "TIME_ZONE_PROVIDED";
+ private static final String LOCALE_PROVIDED_PARAM = "LOCALE_PROVIDED";
+
@NonNull private final ComponentName mDeviceAdminComponentName;
@NonNull private final String mOwnerName;
private final boolean mLeaveAllSystemAppsEnabled;
@@ -121,6 +129,29 @@
}
/**
+ * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+ */
+ public void logParams(@NonNull String callerPackage) {
+ requireNonNull(callerPackage);
+
+ logParam(callerPackage, LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM, mLeaveAllSystemAppsEnabled);
+ logParam(callerPackage, CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS_PARAM,
+ mDeviceOwnerCanGrantSensorsPermissions);
+ logParam(callerPackage, TIME_ZONE_PROVIDED_PARAM, /* value= */ mTimeZone != null);
+ logParam(callerPackage, LOCALE_PROVIDED_PARAM, /* value= */ mLocale != null);
+ }
+
+ private void logParam(String callerPackage, String param, boolean value) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_PARAM)
+ .setStrings(callerPackage)
+ .setAdmin(mDeviceAdminComponentName)
+ .setStrings(param)
+ .setBoolean(value)
+ .write();
+ }
+
+ /**
* Builder class for {@link FullyManagedDeviceProvisioningParams} objects.
*/
public static final class Builder {
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index 5fe63d1..1a6099a 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -25,6 +25,7 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.stats.devicepolicy.DevicePolicyEnums;
/**
* Params required to provision a managed profile, see
@@ -34,6 +35,13 @@
*/
@TestApi
public final class ManagedProfileProvisioningParams implements Parcelable {
+ private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
+ "LEAVE_ALL_SYSTEM_APPS_ENABLED";
+ private static final String ORGANIZATION_OWNED_PROVISIONING_PARAM =
+ "ORGANIZATION_OWNED_PROVISIONING";
+ private static final String ACCOUNT_TO_MIGRATE_PROVIDED_PARAM = "ACCOUNT_TO_MIGRATE_PROVIDED";
+ private static final String KEEP_MIGRATED_ACCOUNT_PARAM = "KEEP_MIGRATED_ACCOUNT";
+
@NonNull private final ComponentName mProfileAdminComponentName;
@NonNull private final String mOwnerName;
@Nullable private final String mProfileName;
@@ -93,6 +101,30 @@
}
/**
+ * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+ */
+ public void logParams(@NonNull String callerPackage) {
+ requireNonNull(callerPackage);
+
+ logParam(callerPackage, LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM, mLeaveAllSystemAppsEnabled);
+ logParam(callerPackage, ORGANIZATION_OWNED_PROVISIONING_PARAM,
+ mOrganizationOwnedProvisioning);
+ logParam(callerPackage, KEEP_MIGRATED_ACCOUNT_PARAM, mKeepAccountMigrated);
+ logParam(callerPackage, ACCOUNT_TO_MIGRATE_PROVIDED_PARAM,
+ /* value= */ mAccountToMigrate != null);
+ }
+
+ private void logParam(String callerPackage, String param, boolean value) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_PARAM)
+ .setStrings(callerPackage)
+ .setAdmin(mProfileAdminComponentName)
+ .setStrings(param)
+ .setBoolean(value)
+ .write();
+ }
+
+ /**
* Builder class for {@link ManagedProfileProvisioningParams} objects.
*/
public static final class Builder {
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/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
index 1e0653d..920b9fe 100644
--- a/core/java/android/app/smartspace/SmartspaceTargetEvent.java
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -138,6 +138,15 @@
dest.writeInt(mEventType);
}
+ @Override
+ public String toString() {
+ return "SmartspaceTargetEvent{"
+ + "mSmartspaceTarget=" + mSmartspaceTarget
+ + ", mSmartspaceActionId='" + mSmartspaceActionId + '\''
+ + ", mEventType=" + mEventType
+ + '}';
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 2c1e951..30ea5c4 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -30,7 +30,7 @@
interface IUsageStatsManager {
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
ParceledListSlice queryUsageStats(int bucketType, long beginTime, long endTime,
- String callingPackage);
+ String callingPackage, int userId);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
ParceledListSlice queryConfigurationStats(int bucketType, long beginTime, long endTime,
String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index f74d16e..31781ec 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UserHandleAware;
import android.app.Activity;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
@@ -437,11 +438,12 @@
* @see #INTERVAL_YEARLY
* @see #INTERVAL_BEST
*/
+ @UserHandleAware
public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
try {
@SuppressWarnings("unchecked")
ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,
- endTime, mContext.getOpPackageName());
+ endTime, mContext.getOpPackageName(), mContext.getUserId());
if (slice != null) {
return slice.getList();
}
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java
index 960a087..9007d9d 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/Association.java
@@ -23,6 +23,7 @@
import com.android.internal.util.DataClass;
+import java.util.Date;
import java.util.Objects;
/**
@@ -39,12 +40,19 @@
private final @NonNull String mPackageName;
private final @Nullable String mDeviceProfile;
private final boolean mNotifyOnDeviceNearby;
+ private final long mTimeApprovedMs;
/** @hide */
public int getUserId() {
return mUserId;
}
+ private String timeApprovedMsToString() {
+ return new Date(mTimeApprovedMs).toString();
+ }
+
+
+
// Code below generated by codegen v1.0.22.
@@ -71,7 +79,8 @@
@NonNull String deviceMacAddress,
@NonNull String packageName,
@Nullable String deviceProfile,
- boolean notifyOnDeviceNearby) {
+ boolean notifyOnDeviceNearby,
+ long timeApprovedMs) {
this.mUserId = userId;
com.android.internal.util.AnnotationValidations.validate(
UserIdInt.class, null, mUserId);
@@ -83,6 +92,7 @@
NonNull.class, null, mPackageName);
this.mDeviceProfile = deviceProfile;
this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ this.mTimeApprovedMs = timeApprovedMs;
// onConstructed(); // You can define this method to get a callback
}
@@ -107,6 +117,11 @@
return mNotifyOnDeviceNearby;
}
+ @DataClass.Generated.Member
+ public long getTimeApprovedMs() {
+ return mTimeApprovedMs;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -118,7 +133,8 @@
"deviceMacAddress = " + mDeviceMacAddress + ", " +
"packageName = " + mPackageName + ", " +
"deviceProfile = " + mDeviceProfile + ", " +
- "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby +
+ "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby + ", " +
+ "timeApprovedMs = " + timeApprovedMsToString() +
" }";
}
@@ -139,7 +155,8 @@
&& Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
&& Objects.equals(mPackageName, that.mPackageName)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
- && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby;
+ && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
+ && mTimeApprovedMs == that.mTimeApprovedMs;
}
@Override
@@ -154,6 +171,7 @@
_hash = 31 * _hash + Objects.hashCode(mPackageName);
_hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
_hash = 31 * _hash + Boolean.hashCode(mNotifyOnDeviceNearby);
+ _hash = 31 * _hash + Long.hashCode(mTimeApprovedMs);
return _hash;
}
@@ -171,6 +189,7 @@
dest.writeString(mDeviceMacAddress);
dest.writeString(mPackageName);
if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
+ dest.writeLong(mTimeApprovedMs);
}
@Override
@@ -190,6 +209,7 @@
String deviceMacAddress = in.readString();
String packageName = in.readString();
String deviceProfile = (flg & 0x8) == 0 ? null : in.readString();
+ long timeApprovedMs = in.readLong();
this.mUserId = userId;
com.android.internal.util.AnnotationValidations.validate(
@@ -202,6 +222,7 @@
NonNull.class, null, mPackageName);
this.mDeviceProfile = deviceProfile;
this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ this.mTimeApprovedMs = timeApprovedMs;
// onConstructed(); // You can define this method to get a callback
}
@@ -221,10 +242,10 @@
};
@DataClass.Generated(
- time = 1610482674799L,
+ time = 1612832377589L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/companion/Association.java",
- inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mNotifyOnDeviceNearby\npublic int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mNotifyOnDeviceNearby\nprivate final long mTimeApprovedMs\npublic int getUserId()\nprivate java.lang.String timeApprovedMsToString()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 527d8df..95d3515 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -49,4 +49,6 @@
void registerDevicePresenceListenerService(in String packageName, in String deviceAddress);
void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress);
+
+ boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
}
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/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/tools/hiddenapi/Android.bp b/core/java/android/content/om/OverlayIdentifier.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to core/java/android/content/om/OverlayIdentifier.aidl
index e0eb06cb..d1c7770 100644
--- a/tools/hiddenapi/Android.bp
+++ b/core/java/android/content/om/OverlayIdentifier.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-python_binary_host {
- name: "merge_csv",
- main: "merge_csv.py",
- srcs: ["merge_csv.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: true
- },
- },
-}
+package android.content.om;
+
+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/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..a2e533a 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -30,6 +30,7 @@
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 +279,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 +313,7 @@
PROTECTION_FLAG_RETAIL_DEMO,
PROTECTION_FLAG_RECENTS,
PROTECTION_FLAG_ROLE,
+ PROTECTION_FLAG_KNOWN_SIGNER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -466,6 +477,15 @@
*/
public @Nullable CharSequence nonLocalizedDescription;
+ /**
+ * 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 +590,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();
}
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..35bb33c 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -26,6 +26,8 @@
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import java.util.Set;
+
/** @hide */
public class ParsedPermission extends ParsedComponent {
@@ -39,6 +41,8 @@
boolean tree;
@Nullable
private ParsedPermissionGroup parsedPermissionGroup;
+ @Nullable
+ Set<String> knownCerts;
@VisibleForTesting
public ParsedPermission() {
@@ -81,6 +85,10 @@
return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
}
+ public @Nullable Set<String> getKnownCerts() {
+ return knownCerts;
+ }
+
public int calculateFootprint() {
int size = getName().length();
if (getNonLocalizedLabel() != null) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 9012b5ce..a7cecbe 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -25,6 +25,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.R;
@@ -32,6 +33,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Locale;
+import java.util.Set;
/** @hide */
public class ParsedPermissionUtils {
@@ -90,6 +93,43 @@
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) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ permission.knownCerts = new ArraySet<>();
+ for (String knownCert : knownCerts) {
+ permission.knownCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
+ } else {
+ final String knownCert = res.getString(knownCertsResource);
+ if (knownCert != null) {
+ permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+ }
+ }
+ 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.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+ }
+ }
+
// 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/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/tools/hiddenapi/Android.bp b/core/java/android/hardware/face/FaceAuthenticationFrame.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to core/java/android/hardware/face/FaceAuthenticationFrame.aidl
index e0eb06cb..4dc41f1 100644
--- a/tools/hiddenapi/Android.bp
+++ b/core/java/android/hardware/face/FaceAuthenticationFrame.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -13,18 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.face;
-python_binary_host {
- name: "merge_csv",
- main: "merge_csv.py",
- srcs: ["merge_csv.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: true
- },
- },
-}
+/**
+ * @hide
+ */
+parcelable FaceAuthenticationFrame;
diff --git a/core/java/android/hardware/face/FaceDataFrame.java b/core/java/android/hardware/face/FaceDataFrame.java
index 3a0e09b..092359c 100644
--- a/core/java/android/hardware/face/FaceDataFrame.java
+++ b/core/java/android/hardware/face/FaceDataFrame.java
@@ -63,6 +63,22 @@
}
/**
+ * A container for data common to {@link FaceAuthenticationFrame} and {@link FaceEnrollFrame}.
+ *
+ * @param acquiredInfo An integer corresponding to a known acquired message.
+ * @param vendorCode An integer representing a custom vendor-specific message. Ignored unless
+ * {@code acquiredInfo} is {@code FACE_ACQUIRED_VENDOR}.
+ */
+ public FaceDataFrame(int acquiredInfo, int vendorCode) {
+ mAcquiredInfo = acquiredInfo;
+ mVendorCode = vendorCode;
+ mPan = 0f;
+ mTilt = 0f;
+ mDistance = 0f;
+ mIsCancellable = false;
+ }
+
+ /**
* @return An integer corresponding to a known acquired message.
*
* @see android.hardware.biometrics.BiometricFaceConstants
diff --git a/tools/hiddenapi/Android.bp b/core/java/android/hardware/face/FaceEnrollFrame.aidl
similarity index 68%
copy from tools/hiddenapi/Android.bp
copy to core/java/android/hardware/face/FaceEnrollFrame.aidl
index e0eb06cb..b854681 100644
--- a/tools/hiddenapi/Android.bp
+++ b/core/java/android/hardware/face/FaceEnrollFrame.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -13,18 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.face;
-python_binary_host {
- name: "merge_csv",
- main: "merge_csv.py",
- srcs: ["merge_csv.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: true
- },
- },
-}
+/**
+ * @hide
+ */
+parcelable FaceEnrollFrame;
diff --git a/core/java/android/hardware/face/FaceEnrollStage.java b/core/java/android/hardware/face/FaceEnrollStage.java
index 03dba55..de717fb 100644
--- a/core/java/android/hardware/face/FaceEnrollStage.java
+++ b/core/java/android/hardware/face/FaceEnrollStage.java
@@ -28,6 +28,7 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
+ FaceEnrollStage.UNKNOWN,
FaceEnrollStage.FIRST_FRAME_RECEIVED,
FaceEnrollStage.WAITING_FOR_CENTERING,
FaceEnrollStage.HOLD_STILL_IN_CENTER,
@@ -37,6 +38,11 @@
})
public @interface FaceEnrollStage {
/**
+ * The current enrollment stage is not known.
+ */
+ int UNKNOWN = -1;
+
+ /**
* Enrollment has just begun. No action is needed from the user yet.
*/
int FIRST_FRAME_RECEIVED = 0;
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 588bc01..f3da6a9 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -71,17 +71,19 @@
private static final int MSG_FACE_DETECTED = 109;
private static final int MSG_CHALLENGE_INTERRUPTED = 110;
private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;
+ private static final int MSG_AUTHENTICATION_FRAME = 112;
+ private static final int MSG_ENROLLMENT_FRAME = 113;
private final IFaceService mService;
private final Context mContext;
private IBinder mToken = new Binder();
- private AuthenticationCallback mAuthenticationCallback;
- private FaceDetectionCallback mFaceDetectionCallback;
- private EnrollmentCallback mEnrollmentCallback;
- private RemovalCallback mRemovalCallback;
- private SetFeatureCallback mSetFeatureCallback;
- private GetFeatureCallback mGetFeatureCallback;
- private GenerateChallengeCallback mGenerateChallengeCallback;
+ @Nullable private AuthenticationCallback mAuthenticationCallback;
+ @Nullable private FaceDetectionCallback mFaceDetectionCallback;
+ @Nullable private EnrollmentCallback mEnrollmentCallback;
+ @Nullable private RemovalCallback mRemovalCallback;
+ @Nullable private SetFeatureCallback mSetFeatureCallback;
+ @Nullable private GetFeatureCallback mGetFeatureCallback;
+ @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
private CryptoObject mCryptoObject;
private Face mRemovalFace;
private Handler mHandler;
@@ -154,6 +156,16 @@
public void onChallengeInterruptFinished(int sensorId) {
mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
}
+
+ @Override
+ public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+ }
+
+ @Override
+ public void onEnrollmentFrame(FaceEnrollFrame frame) {
+ mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+ }
};
/**
@@ -1248,6 +1260,12 @@
case MSG_CHALLENGE_INTERRUPT_FINISHED:
sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
break;
+ case MSG_AUTHENTICATION_FRAME:
+ sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+ break;
+ case MSG_ENROLLMENT_FRAME:
+ sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+ break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
}
@@ -1349,15 +1367,52 @@
private void sendAcquiredResult(int acquireInfo, int vendorCode) {
if (mAuthenticationCallback != null) {
+ final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendAuthenticationFrame(frame);
+ } else if (mEnrollmentCallback != null) {
+ final FaceEnrollFrame frame = new FaceEnrollFrame(
+ null /* cell */,
+ FaceEnrollStage.UNKNOWN,
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendEnrollmentFrame(frame);
+ }
+ }
+
+ private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null authentication frame");
+ } else if (mAuthenticationCallback != null) {
+ // TODO(b/178414967): Send additional frame data to callback
+ 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);
mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+
+ // Ensure that only non-null help messages are sent.
+ if (helpMessage != null) {
+ mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
+ }
}
- final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
- final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
- ? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
- } else if (mAuthenticationCallback != null && msg != null) {
- mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ }
+
+ private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null enrollment frame");
+ } else if (mEnrollmentCallback != null) {
+ // TODO(b/178414967): Send additional frame data to callback
+ 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);
+ mEnrollmentCallback.onEnrollmentHelp(helpCode, helpMessage);
}
}
+
+ private static int getHelpCode(int acquireInfo, int vendorCode) {
+ return acquireInfo == FACE_ACQUIRED_VENDOR
+ ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
+ : acquireInfo;
+ }
}
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index bd4d3a0..2ef1430 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -16,6 +16,8 @@
package android.hardware.face;
import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
/**
* Communication channel from the FaceService back to FaceAuthenticationManager.
@@ -34,4 +36,6 @@
void onChallengeGenerated(int sensorId, long challenge);
void onChallengeInterrupted(int sensorId);
void onChallengeInterruptFinished(int sensorId);
+ void onAuthenticationFrame(in FaceAuthenticationFrame frame);
+ void onEnrollmentFrame(in FaceEnrollFrame frame);
}
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/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/net/VpnTransportInfo.java b/core/java/android/net/VpnTransportInfo.java
new file mode 100644
index 0000000..082fa58
--- /dev/null
+++ b/core/java/android/net/VpnTransportInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.util.Objects;
+
+/** @hide */
+public final class VpnTransportInfo implements TransportInfo, Parcelable {
+ private static final SparseArray<String> sTypeToString =
+ MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
+
+ /** Type of this VPN. */
+ @VpnManager.VpnType public final int type;
+
+ public VpnTransportInfo(@VpnManager.VpnType int type) {
+ this.type = type;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VpnTransportInfo)) return false;
+
+ VpnTransportInfo that = (VpnTransportInfo) o;
+ return this.type == that.type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type);
+ }
+
+ @Override
+ public String toString() {
+ final String typeString = sTypeToString.get(type, "VPN_TYPE_???");
+ return String.format("VpnTransportInfo{%s}", typeString);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(type);
+ }
+
+ public static final @NonNull Creator<VpnTransportInfo> CREATOR =
+ new Creator<VpnTransportInfo>() {
+ public VpnTransportInfo createFromParcel(Parcel in) {
+ return new VpnTransportInfo(in.readInt());
+ }
+ public VpnTransportInfo[] newArray(int size) {
+ return new VpnTransportInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index fa090f5..1a38338 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -28,8 +28,10 @@
import android.os.ServiceSpecificException;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
import java.io.IOException;
+import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -67,8 +69,7 @@
public class VcnManager {
@NonNull private static final String TAG = VcnManager.class.getSimpleName();
- @VisibleForTesting
- public static final Map<
+ private static final Map<
VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>();
@@ -88,6 +89,18 @@
mService = requireNonNull(service, "missing service");
}
+ /**
+ * Get all currently registered VcnUnderlyingNetworkPolicyListeners for testing purposes.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ @NonNull
+ public static Map<VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
+ getAllPolicyListeners() {
+ return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
+ }
+
// TODO: Make setVcnConfig(), clearVcnConfig() Public API
/**
* Sets the VCN configuration for a given subscription group.
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/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index af8e8de..305815f 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -33,20 +33,33 @@
private final int mDischargePercentage;
private final ArrayList<UidBatteryConsumer> mUidBatteryConsumers;
private final ArrayList<SystemBatteryConsumer> mSystemBatteryConsumers;
+ private final ArrayList<UserBatteryConsumer> mUserBatteryConsumers;
private BatteryUsageStats(@NonNull Builder builder) {
mConsumedPower = builder.mConsumedPower;
mDischargePercentage = builder.mDischargePercentage;
+
int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size();
mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount);
for (int i = 0; i < uidBatteryConsumerCount; i++) {
- mUidBatteryConsumers.add(builder.mUidBatteryConsumerBuilders.valueAt(i).build());
+ UidBatteryConsumer.Builder uidBatteryConsumerBuilder =
+ builder.mUidBatteryConsumerBuilders.valueAt(i);
+ if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) {
+ mUidBatteryConsumers.add(uidBatteryConsumerBuilder.build());
+ }
}
+
int systemBatteryConsumerCount = builder.mSystemBatteryConsumerBuilders.size();
mSystemBatteryConsumers = new ArrayList<>(systemBatteryConsumerCount);
for (int i = 0; i < systemBatteryConsumerCount; i++) {
mSystemBatteryConsumers.add(builder.mSystemBatteryConsumerBuilders.valueAt(i).build());
}
+
+ int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size();
+ mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount);
+ for (int i = 0; i < userBatteryConsumerCount; i++) {
+ mUserBatteryConsumers.add(builder.mUserBatteryConsumerBuilders.valueAt(i).build());
+ }
}
/**
@@ -75,6 +88,11 @@
return mSystemBatteryConsumers;
}
+ @NonNull
+ public List<UserBatteryConsumer> getUserBatteryConsumers() {
+ return mUserBatteryConsumers;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -85,6 +103,8 @@
source.readParcelableList(mUidBatteryConsumers, getClass().getClassLoader());
mSystemBatteryConsumers = new ArrayList<>();
source.readParcelableList(mSystemBatteryConsumers, getClass().getClassLoader());
+ mUserBatteryConsumers = new ArrayList<>();
+ source.readParcelableList(mUserBatteryConsumers, getClass().getClassLoader());
mConsumedPower = source.readDouble();
mDischargePercentage = source.readInt();
}
@@ -93,6 +113,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelableList(mUidBatteryConsumers, flags);
dest.writeParcelableList(mSystemBatteryConsumers, flags);
+ dest.writeParcelableList(mUserBatteryConsumers, flags);
dest.writeDouble(mConsumedPower);
dest.writeInt(mDischargePercentage);
}
@@ -120,6 +141,8 @@
new SparseArray<>();
private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders =
new SparseArray<>();
+ private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
+ new SparseArray<>();
public Builder(int customPowerComponentCount, int customTimeComponentCount) {
mCustomPowerComponentCount = customPowerComponentCount;
@@ -137,7 +160,6 @@
/**
* Sets the battery discharge amount since BatteryStats reset as percentage of the full
* charge.
- *
*/
@SuppressLint("PercentageInt") // See b/174188159
@NonNull
@@ -173,8 +195,8 @@
}
/**
- * Creates or returns a exiting UidBatteryConsumer, which represents battery attribution
- * data for an individual UID.
+ * Creates or returns a exiting SystemBatteryConsumer, which represents battery attribution
+ * data for a specific drain type.
*/
@NonNull
public SystemBatteryConsumer.Builder getOrCreateSystemBatteryConsumerBuilder(
@@ -188,6 +210,21 @@
return builder;
}
+ /**
+ * Creates or returns a exiting UserBatteryConsumer, which represents battery attribution
+ * data for an individual {@link UserHandle}.
+ */
+ @NonNull
+ public UserBatteryConsumer.Builder getOrCreateUserBatteryConsumerBuilder(int userId) {
+ UserBatteryConsumer.Builder builder = mUserBatteryConsumerBuilders.get(userId);
+ if (builder == null) {
+ builder = new UserBatteryConsumer.Builder(mCustomPowerComponentCount,
+ mCustomTimeComponentCount, userId);
+ mUserBatteryConsumerBuilders.put(userId, builder);
+ }
+ return builder;
+ }
+
@NonNull
public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() {
return mUidBatteryConsumerBuilders;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 48e7389..5b5fe1d 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.util.IntArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,9 +54,13 @@
public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 1;
private final int mFlags;
+ @NonNull
+ private final int[] mUserIds;
private BatteryUsageStatsQuery(@NonNull Builder builder) {
mFlags = builder.mFlags;
+ mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
+ : new int[]{UserHandle.USER_ALL};
}
@BatteryUsageStatsFlags
@@ -63,13 +68,28 @@
return mFlags;
}
+ /**
+ * Returns an array of users for which the attribution is requested. It may
+ * contain {@link UserHandle#USER_ALL} to indicate that the attribution
+ * should be performed for all users. Battery consumed by users <b>not</b> included
+ * in this array will be returned in the aggregated form as {@link UserBatteryConsumer}'s.
+ */
+ @NonNull
+ public int[] getUserIds() {
+ return mUserIds;
+ }
+
private BatteryUsageStatsQuery(Parcel in) {
mFlags = in.readInt();
+ mUserIds = new int[in.readInt()];
+ in.readIntArray(mUserIds);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mFlags);
+ dest.writeInt(mUserIds.length);
+ dest.writeIntArray(mUserIds);
}
@Override
@@ -96,6 +116,7 @@
*/
public static final class Builder {
private int mFlags;
+ private IntArray mUserIds;
/**
* Builds a read-only BatteryUsageStatsQuery object.
@@ -105,6 +126,18 @@
}
/**
+ * Add a user whose battery stats should be included in the battery usage stats.
+ * {@link UserHandle#USER_ALL} will be used by default if no users are added explicitly.
+ */
+ public Builder addUser(@NonNull UserHandle userHandle) {
+ if (mUserIds == null) {
+ mUserIds = new IntArray(1);
+ }
+ mUserIds.add(userHandle.getIdentifier());
+ return this;
+ }
+
+ /**
* Sets flags to modify the behavior of {@link BatteryStatsManager#getBatteryUsageStats}.
*/
public Builder setFlags(@BatteryUsageStatsFlags int flags) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 9584bc7..6a76da2 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2557,6 +2557,14 @@
public static native long getZramFreeKb();
/**
+ * Return total memory size in kilobytes for exported DMA-BUFs or -1 if
+ * the DMA-BUF sysfs stats at /sys/kernel/dmabuf/buffers could not be read.
+ *
+ * @hide
+ */
+ public static native long getDmabufTotalExportedKb();
+
+ /**
* Return memory size in kilobytes allocated for ION heaps or -1 if
* /sys/kernel/ion/total_heaps_kb could not be read.
*
@@ -2565,6 +2573,14 @@
public static native long getIonHeapsSizeKb();
/**
+ * Return memory size in kilobytes allocated for DMA-BUF heap pools or -1 if
+ * /sys/kernel/dma_heap/total_pools_kb could not be read.
+ *
+ * @hide
+ */
+ public static native long getDmabufHeapPoolsSizeKb();
+
+ /**
* Return memory size in kilobytes allocated for ION pools or -1 if
* /sys/kernel/ion/total_pools_kb could not be read.
*
@@ -2573,13 +2589,13 @@
public static native long getIonPoolsSizeKb();
/**
- * Return ION memory mapped by processes in kB.
+ * Return DMA-BUF memory mapped by processes in kB.
* Notes:
* * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
*
* @hide
*/
- public static native long getIonMappedSizeKb();
+ public static native long getDmabufMappedSizeKb();
/**
* Return memory size in kilobytes used by GPU.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 518e29d..124c0b0 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.AppGlobals;
@@ -836,6 +837,21 @@
public static String DIRECTORY_AUDIOBOOKS = "Audiobooks";
/**
+ * Standard directory in which to place any audio files which are
+ * recordings.
+ */
+ @NonNull
+ // The better way is that expose a static method getRecordingDirectories.
+ // But since it's an existing API surface and developers already
+ // used to DIRECTORY_* constants, we should keep using this pattern
+ // for consistency. We use SuppressLint here to avoid exposing a final
+ // field. A final field will prevent us from ever changing the value of
+ // DIRECTORY_RECORDINGS. Not that it's likely that we will ever need to
+ // change it, but it's better to have such option.
+ @SuppressLint({"MutableBareField", "AllUpper"})
+ public static String DIRECTORY_RECORDINGS = "Recordings";
+
+ /**
* List of standard storage directories.
* <p>
* Each of its values have its own constant:
@@ -851,6 +867,7 @@
* <li>{@link #DIRECTORY_DCIM}
* <li>{@link #DIRECTORY_DOCUMENTS}
* <li>{@link #DIRECTORY_AUDIOBOOKS}
+ * <li>{@link #DIRECTORY_RECORDINGS}
* </ul>
* @hide
*/
@@ -866,6 +883,7 @@
DIRECTORY_DCIM,
DIRECTORY_DOCUMENTS,
DIRECTORY_AUDIOBOOKS,
+ DIRECTORY_RECORDINGS,
};
/**
@@ -891,6 +909,7 @@
/** {@hide} */ public static final int HAS_DCIM = 1 << 8;
/** {@hide} */ public static final int HAS_DOCUMENTS = 1 << 9;
/** {@hide} */ public static final int HAS_AUDIOBOOKS = 1 << 10;
+ /** {@hide} */ public static final int HAS_RECORDINGS = 1 << 11;
/** {@hide} */ public static final int HAS_ANDROID = 1 << 16;
/** {@hide} */ public static final int HAS_OTHER = 1 << 17;
@@ -921,6 +940,7 @@
else if (DIRECTORY_DCIM.equals(name)) res |= HAS_DCIM;
else if (DIRECTORY_DOCUMENTS.equals(name)) res |= HAS_DOCUMENTS;
else if (DIRECTORY_AUDIOBOOKS.equals(name)) res |= HAS_AUDIOBOOKS;
+ else if (DIRECTORY_RECORDINGS.equals(name)) res |= HAS_RECORDINGS;
else if (DIRECTORY_ANDROID.equals(name)) res |= HAS_ANDROID;
else res |= HAS_OTHER;
}
@@ -1339,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/Process.java b/core/java/android/os/Process.java
index 39e3e14..8068c87 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1442,7 +1442,7 @@
*
* @hide
*/
- public static boolean hasFileLocks(int pid) throws IOException {
+ public static boolean hasFileLocks(int pid) throws Exception {
BufferedReader br = null;
try {
@@ -1454,8 +1454,13 @@
for (int i = 0; i < 5 && st.hasMoreTokens(); i++) {
String str = st.nextToken();
- if (i == 4 && Integer.parseInt(str) == pid) {
- return true;
+ try {
+ if (i == 4 && Integer.parseInt(str) == pid) {
+ return true;
+ }
+ } catch (NumberFormatException nfe) {
+ throw new Exception("Exception parsing /proc/locks at \" "
+ + line + " \", token #" + i);
}
}
}
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 3ffaa9e..a828077 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -96,14 +96,16 @@
private final int mUid;
private String mPackageWithHighestDrain;
private boolean mSystemComponent;
+ 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;
}
@@ -113,14 +115,6 @@
}
/**
- * Creates a read-only object out of the Builder values.
- */
- @NonNull
- public UidBatteryConsumer build() {
- return new UidBatteryConsumer(this);
- }
-
- /**
* Sets the name of the package owned by this UID that consumed the highest amount
* of power since BatteryStats reset.
*/
@@ -131,12 +125,27 @@
}
/**
- * Marks the UidBatteryConsumer as part of the system. For example,
- * the UidBatteryConsumer with the UID {@link Process#BLUETOOTH_UID} is considered
- * as a system component.
+ * Marks the UidBatteryConsumer for exclusion from the result set.
*/
- public void setSystemComponent(boolean systemComponent) {
- mSystemComponent = systemComponent;
+ public Builder excludeFromBatteryUsageStats() {
+ mExcludeFromBatteryUsageStats = true;
+ return this;
+ }
+
+ /**
+ * Returns true if this UidBatteryConsumer must be excluded from the
+ * BatteryUsageStats.
+ */
+ public boolean isExcludedFromBatteryUsageStats() {
+ return mExcludeFromBatteryUsageStats;
+ }
+
+ /**
+ * Creates a read-only object out of the Builder values.
+ */
+ @NonNull
+ public UidBatteryConsumer build() {
+ return new UidBatteryConsumer(this);
}
}
}
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
new file mode 100644
index 0000000..94e567f
--- /dev/null
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -0,0 +1,115 @@
+/*
+ * 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.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains power consumption data attributed to a {@link UserHandle}.
+ *
+ * {@hide}
+ */
+public class UserBatteryConsumer extends BatteryConsumer implements Parcelable {
+ private final int mUserId;
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ private UserBatteryConsumer(@NonNull UserBatteryConsumer.Builder builder) {
+ super(builder.mPowerComponentsBuilder.build());
+ mUserId = builder.mUserId;
+ }
+
+ private UserBatteryConsumer(Parcel in) {
+ super(new PowerComponents(in));
+ mUserId = in.readInt();
+ }
+
+ /**
+ * Writes the contents into a Parcel.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mUserId);
+ }
+
+ public static final Creator<UserBatteryConsumer> CREATOR =
+ new Creator<UserBatteryConsumer>() {
+ @Override
+ public UserBatteryConsumer createFromParcel(Parcel in) {
+ return new UserBatteryConsumer(in);
+ }
+
+ @Override
+ public UserBatteryConsumer[] newArray(int size) {
+ return new UserBatteryConsumer[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Builder for UserBatteryConsumer.
+ */
+ public static final class Builder extends BaseBuilder<Builder> {
+ private final int mUserId;
+ private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
+
+ Builder(int customPowerComponentCount, int customTimeComponentCount, int userId) {
+ super(customPowerComponentCount, customTimeComponentCount);
+ mUserId = userId;
+ }
+
+ /**
+ * Add a UidBatteryConsumer to this UserBatteryConsumer.
+ * <p>
+ * Calculated power and duration components of the added UID battery consumers
+ * are aggregated at the time the UserBatteryConsumer is built by the {@link #build()}
+ * method.
+ * </p>
+ */
+ public void addUidBatteryConsumer(UidBatteryConsumer.Builder uidBatteryConsumerBuilder) {
+ if (mUidBatteryConsumers == null) {
+ mUidBatteryConsumers = new ArrayList<>();
+ }
+ mUidBatteryConsumers.add(uidBatteryConsumerBuilder);
+ }
+
+ /**
+ * Creates a read-only object out of the Builder values.
+ */
+ @NonNull
+ public UserBatteryConsumer build() {
+ if (mUidBatteryConsumers != null) {
+ for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
+ UidBatteryConsumer.Builder uidBatteryConsumer = mUidBatteryConsumers.get(i);
+ mPowerComponentsBuilder.addPowerAndDuration(
+ uidBatteryConsumer.mPowerComponentsBuilder);
+ }
+ }
+ return new UserBatteryConsumer(this);
+ }
+ }
+}
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index faa90d9..86b20c4 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -350,6 +350,9 @@
return cachedTypeface;
}
+ Log.w(TAG, "Platform version of downloadable fonts is deprecated. Please use"
+ + " androidx version instead.");
+
synchronized (sLock) {
// It is possible that Font is loaded during the thread sleep time
// re-check the cache to avoid re-loading the font
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 990b7bd..dad932c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -123,14 +123,6 @@
boolean outOfMemory(IWindow window);
/**
- * Give the window manager a hint of the part of the window that is
- * completely transparent, allowing it to work with the surface flinger
- * to optimize compositing of this part of the window.
- */
- @UnsupportedAppUsage
- oneway void setTransparentRegion(IWindow window, in Region region);
-
- /**
* Tell the window manager about the content and visible insets of the
* given window, which can be used to adjust the <var>outContentInsets</var>
* and <var>outVisibleInsets</var> values returned by
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/View.java b/core/java/android/view/View.java
index e1ccc51..1273b49 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -740,6 +740,7 @@
* @attr ref android.R.styleable#View_alpha
* @attr ref android.R.styleable#View_background
* @attr ref android.R.styleable#View_clickable
+ * @attr ref android.R.styleable#View_clipToOutline
* @attr ref android.R.styleable#View_contentDescription
* @attr ref android.R.styleable#View_drawingCacheQuality
* @attr ref android.R.styleable#View_duplicateParentState
@@ -5968,6 +5969,9 @@
case R.styleable.View_scrollCaptureHint:
setScrollCaptureHint((a.getInt(attr, SCROLL_CAPTURE_HINT_AUTO)));
break;
+ case R.styleable.View_clipToOutline:
+ setClipToOutline(a.getBoolean(attr, false));
+ break;
}
}
@@ -17921,6 +17925,8 @@
*
* @see #setOutlineProvider(ViewOutlineProvider)
* @see #getClipToOutline()
+ *
+ * @attr ref android.R.styleable#View_clipToOutline
*/
@RemotableViewMethod
public void setClipToOutline(boolean clipToOutline) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index acc77b5..755ae31 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1913,25 +1913,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 +3029,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);
@@ -3064,11 +3057,14 @@
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
- // reconfigure window manager
- try {
- mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
- } catch (RemoteException e) {
- }
+ // TODO: Ideally we would do this in prepareSurfaces,
+ // but prepareSurfaces is currently working under
+ // the assumption that we paused the render thread
+ // via the WM relayout code path. We probably eventually
+ // want to synchronize transparent region hint changes
+ // with draws.
+ mTransaction.setTransparentRegionHint(getSurfaceControl(),
+ mTransparentRegion).apply();
}
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 3aedda1..39d3c01 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -314,10 +314,6 @@
}
@Override
- public void setTransparentRegion(android.view.IWindow window, android.graphics.Region region) {
- }
-
- @Override
public void setInsets(android.view.IWindow window, int touchableInsets,
android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets,
android.graphics.Region touchableRegion) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 794181e..decbf8c 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -200,6 +200,34 @@
"android.view.autofill.extra.AUTHENTICATION_RESULT";
/**
+ * Intent extra: The optional boolean extra field provided by the
+ * {@link android.service.autofill.AutofillService} accompanying the {@link
+ * android.service.autofill.Dataset} result of an authentication operation.
+ *
+ * <p> Before {@link android.os.Build.VERSION_CODES#R}, if the authentication result is a
+ * {@link android.service.autofill.Dataset}, it'll be used to autofill the fields, and also
+ * replace the existing dataset in the cached {@link android.service.autofill.FillResponse}.
+ * That means if the user clears the field values, the autofill suggestion will show up again
+ * with the new authenticated Dataset.
+ *
+ * <p> In {@link android.os.Build.VERSION_CODES#R}, we added an exception to this behavior
+ * that if the Dataset being authenticated is a pinned dataset (see
+ * {@link android.service.autofill.InlinePresentation#isPinned()}), the old Dataset will not be
+ * replaced.
+ *
+ * <p> In {@link android.os.Build.VERSION_CODES#S}, we added this boolean extra field to
+ * allow the {@link android.service.autofill.AutofillService} to explicitly specify whether
+ * the returned authenticated Dataset is ephemeral. An ephemeral Dataset will be used to
+ * autofill once and then thrown away. Therefore, when the boolean extra is set to true, the
+ * returned Dataset will not replace the old dataset from the existing
+ * {@link android.service.autofill.FillResponse}. When it's set to false, it will. When it's not
+ * set, the old dataset will be replaced, unless it is a pinned inline suggestion, which is
+ * consistent with the behavior in {@link android.os.Build.VERSION_CODES#R}.
+ */
+ public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET =
+ "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET";
+
+ /**
* Intent extra: The optional extras provided by the
* {@link android.service.autofill.AutofillService}.
*
@@ -1755,6 +1783,11 @@
if (newClientState != null) {
responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
}
+ if (data.getExtras().containsKey(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
+ responseData.putBoolean(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+ data.getBooleanExtra(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+ false));
+ }
try {
mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
mContext.getUserId());
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..61ff36c 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.ColorInt;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -30,6 +31,9 @@
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 +59,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";
@@ -121,6 +143,7 @@
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.
@@ -132,6 +155,8 @@
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);
@@ -309,6 +334,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 +369,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
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index dfef7ca..6cb4b81 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -47,6 +47,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
+import android.graphics.Outline;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -79,6 +80,7 @@
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewManager;
+import android.view.ViewOutlineProvider;
import android.view.ViewParent;
import android.view.ViewStub;
import android.widget.AdapterView.OnItemClickListener;
@@ -194,6 +196,7 @@
private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25;
private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26;
private static final int SET_RADIO_GROUP_CHECKED = 27;
+ private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -2642,6 +2645,88 @@
}
}
+ private static class SetViewOutlinePreferredRadiusAction extends Action {
+
+ private final boolean mIsDimen;
+ private final int mValue;
+
+ SetViewOutlinePreferredRadiusAction(@IdRes int viewId, @DimenRes int dimenResId) {
+ this.viewId = viewId;
+ this.mIsDimen = true;
+ this.mValue = dimenResId;
+ }
+
+ SetViewOutlinePreferredRadiusAction(
+ @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
+ this.viewId = viewId;
+ this.mIsDimen = false;
+ this.mValue = TypedValue.createComplexDimension(radius, units);
+
+ }
+
+ SetViewOutlinePreferredRadiusAction(Parcel in) {
+ viewId = in.readInt();
+ mIsDimen = in.readBoolean();
+ mValue = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(viewId);
+ dest.writeBoolean(mIsDimen);
+ dest.writeInt(mValue);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+ throws ActionException {
+ final View target = root.findViewById(viewId);
+ if (target == null) return;
+
+ float radius;
+ if (mIsDimen) {
+ radius = mValue == 0 ? 0 : target.getResources().getDimension(mValue);
+ } else {
+ radius = TypedValue.complexToDimensionPixelSize(mValue,
+ target.getResources().getDisplayMetrics());
+ }
+ target.setOutlineProvider(new RemoteViewOutlineProvider(radius));
+ }
+
+ @Override
+ public int getActionTag() {
+ return SET_VIEW_OUTLINE_RADIUS_TAG;
+ }
+ }
+
+ /**
+ * OutlineProvider for a view with a radius set by
+ * {@link #setViewOutlinePreferredRadius(int, float, int)}.
+ */
+ public static final class RemoteViewOutlineProvider extends ViewOutlineProvider {
+
+ private final float mRadius;
+
+ public RemoteViewOutlineProvider(float radius) {
+ mRadius = radius;
+ }
+
+ /** Returns the corner radius used when providing the view outline. */
+ public float getRadius() {
+ return mRadius;
+ }
+
+ @Override
+ public void getOutline(@NonNull View view, @NonNull Outline outline) {
+ outline.setRoundRect(
+ 0 /*left*/,
+ 0 /* top */,
+ view.getWidth() /* right */,
+ view.getHeight() /* bottom */,
+ mRadius);
+ }
+ }
+
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
@@ -2860,6 +2945,8 @@
return new SetCompoundButtonCheckedAction(parcel);
case SET_RADIO_GROUP_CHECKED:
return new SetRadioGroupCheckedAction(parcel);
+ case SET_VIEW_OUTLINE_RADIUS_TAG:
+ return new SetViewOutlinePreferredRadiusAction(parcel);
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -3595,6 +3682,28 @@
}
/**
+ * Sets an OutlineProvider on the view whose corner radius is a dimension calculated using
+ * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}. This outline may change shape
+ * during system transitions.
+ *
+ * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
+ * Setting margins in pixels will behave poorly when the RemoteViews object is used on a
+ * display with a different density.
+ */
+ public void setViewOutlinePreferredRadius(
+ @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
+ addAction(new SetViewOutlinePreferredRadiusAction(viewId, radius, units));
+ }
+
+ /**
+ * Sets an OutlineProvider on the view whose corner radius is a dimension resource with
+ * {@code resId}. This outline may change shape during system transitions.
+ */
+ public void setViewOutlinePreferredRadiusDimen(@IdRes int viewId, @DimenRes int resId) {
+ addAction(new SetViewOutlinePreferredRadiusAction(viewId, resId));
+ }
+
+ /**
* Call a method taking one boolean on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
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/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/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 8fe17fb..1313090 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -43,8 +43,7 @@
*/
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
final double powerMah = mPowerEstimator.calculatePower(durationMs);
@@ -84,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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 1f7a7aa..0da2998 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -104,6 +104,7 @@
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.internal.power.MeasuredEnergyStats;
import com.android.internal.power.MeasuredEnergyStats.StandardEnergyBucket;
import com.android.internal.util.ArrayUtils;
@@ -7172,6 +7173,20 @@
.getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
}
+ /**
+ * Returns the energy in microjoules that the given custom energy bucket consumed.
+ * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+ *
+ * @param customEnergyBucket custom energy bucket of interest
+ * @return energy (in microjoules) used by this uid for this energy bucket
+ */
+ public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) {
+ if (mGlobalMeasuredEnergyStats == null) {
+ return ENERGY_DATA_UNAVAILABLE;
+ }
+ return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket);
+ }
+
@Override public long getStartClockTime() {
final long currentTimeMs = System.currentTimeMillis();
if ((currentTimeMs > MILLISECONDS_IN_YEAR
@@ -7940,6 +7955,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 +7979,22 @@
}
/**
+ * Returns the energy used by this uid for a custom energy bucket of interest.
+ * @param customEnergyBucket custom energy bucket of interest
+ * @return energy (in microjoules) used by this uid for this energy bucket
+ */
+ public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) {
+ if (mBsi.mGlobalMeasuredEnergyStats == null
+ || !mBsi.mGlobalMeasuredEnergyStats.isValidCustomBucket(customEnergyBucket)) {
+ return ENERGY_DATA_UNAVAILABLE;
+ }
+ if (mUidMeasuredEnergyStats == null) {
+ return 0L; // It is supported, but was never filled, so it must be 0
+ }
+ return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket);
+ }
+
+ /**
* 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.
*
@@ -10842,6 +10880,10 @@
mSystemServerCpuThreadReader.startTrackingThreadCpuTime();
}
+ public SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes() {
+ return mSystemServerCpuThreadReader.readAbsolute();
+ }
+
public void setCallback(BatteryCallback cb) {
mCallback = cb;
}
@@ -12460,6 +12502,42 @@
}
/**
+ * 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: Worry about uids not in BSI currently, including uninstalled uids 'coming back'
+ // Specifically: What if the uid had been removed? We'll re-create it now.
+ // And if we instead use getAvailableUidStatsLocked() and chec for null, then we might
+ // not create a Uid even when we should be (say, the app's first event, somehow, was to
+ // use GPU). I guess that CPU/kernel data might already have this problem?
+ final Uid uidObj = getUidStatsLocked(uidInt);
+ uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
+ }
+ }
+
+ /**
* Read and record Rail Energy data.
*/
public void updateRailStatsLocked() {
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index e76e34f..094724c 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -24,7 +24,6 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.UserManager;
import android.util.SparseArray;
import java.util.ArrayList;
@@ -89,26 +88,27 @@
final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext,
false /* collectBatteryBroadcast */);
batteryStatsHelper.create((Bundle) null);
- final UserManager userManager = mContext.getSystemService(UserManager.class);
- final List<UserHandle> asUsers = userManager.getUserProfiles();
- final int n = asUsers.size();
- SparseArray<UserHandle> users = new SparseArray<>(n);
- for (int i = 0; i < n; ++i) {
- UserHandle userHandle = asUsers.get(i);
- users.put(userHandle.getIdentifier(), userHandle);
+ final List<UserHandle> users = new ArrayList<>();
+ for (int i = 0; i < queries.size(); i++) {
+ BatteryUsageStatsQuery query = queries.get(i);
+ for (int userId : query.getUserIds()) {
+ UserHandle userHandle = UserHandle.of(userId);
+ if (!users.contains(userHandle)) {
+ users.add(userHandle);
+ }
+ }
}
-
batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users);
ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size());
for (int i = 0; i < queries.size(); i++) {
- results.add(getBatteryUsageStats(queries.get(i), batteryStatsHelper, users));
+ results.add(getBatteryUsageStats(queries.get(i), batteryStatsHelper));
}
return results;
}
private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query,
- BatteryStatsHelper batteryStatsHelper, SparseArray<UserHandle> users) {
+ BatteryStatsHelper batteryStatsHelper) {
// TODO(b/174186358): read extra power component number from configuration
final int customPowerComponentCount = 0;
final int customTimeComponentCount = 0;
@@ -128,8 +128,8 @@
final List<PowerCalculator> powerCalculators = getPowerCalculators();
for (PowerCalculator powerCalculator : powerCalculators) {
- powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs, query,
- users);
+ powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
+ query);
}
return batteryUsageStatsBuilder.build();
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 4c3b950..7d42de4 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -50,8 +50,7 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
if (!mHasBluetoothPowerController || !batteryStats.hasBluetoothActivityReporting()) {
return;
}
@@ -68,7 +67,7 @@
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
calculateApp(app, total);
if (app.getUid() == Process.BLUETOOTH_UID) {
- app.setSystemComponent(true);
+ app.excludeFromBatteryUsageStats();
systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
}
}
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 11c8761..45d8128 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -17,81 +17,166 @@
import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.List;
public class CpuPowerCalculator extends PowerCalculator {
private static final String TAG = "CpuPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
- private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
- private final PowerProfile mProfile;
+ private final int mNumCpuClusters;
+
+ // Time-in-state based CPU power estimation model computes the estimated power
+ // by adding up three components:
+ // - CPU Active power: the constant amount of charge consumed by the CPU when it is on
+ // - Per Cluster power: the additional amount of charge consumed by a CPU cluster
+ // when it is running
+ // - Per frequency power: the additional amount of charge caused by dynamic frequency scaling
+
+ private final UsageBasedPowerEstimator mCpuActivePowerEstimator;
+ // One estimator per cluster
+ private final UsageBasedPowerEstimator[] mPerClusterPowerEstimators;
+ // Multiple estimators per cluster: one per available scaling frequency. Note that different
+ // clusters have different sets of frequencies and corresponding power consumption averages.
+ private final UsageBasedPowerEstimator[][] mPerCpuFreqPowerEstimators;
+
+ private static class Result {
+ public long durationMs;
+ public double powerMah;
+ public long durationFgMs;
+ public String packageWithHighestDrain;
+ }
public CpuPowerCalculator(PowerProfile profile) {
- mProfile = profile;
+ mNumCpuClusters = profile.getNumCpuClusters();
+
+ mCpuActivePowerEstimator = new UsageBasedPowerEstimator(
+ profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE));
+
+ mPerClusterPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters];
+ for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+ mPerClusterPowerEstimators[cluster] = new UsageBasedPowerEstimator(
+ profile.getAveragePowerForCpuCluster(cluster));
+ }
+
+ mPerCpuFreqPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters][];
+ for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+ final int speedsForCluster = profile.getNumSpeedStepsInCpuCluster(cluster);
+ mPerCpuFreqPowerEstimators[cluster] = new UsageBasedPowerEstimator[speedsForCluster];
+ for (int speed = 0; speed < speedsForCluster; speed++) {
+ mPerCpuFreqPowerEstimators[cluster][speed] =
+ new UsageBasedPowerEstimator(
+ profile.getAveragePowerForCpuCore(cluster, speed));
+ }
+ }
}
@Override
- protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
- final int statsType = BatteryStats.STATS_SINCE_CHARGED;
+ Result result = new Result();
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ calculateApp(app, app.getBatteryStatsUid(), result);
+ }
+ }
- long cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
- final int numClusters = mProfile.getNumCpuClusters();
+ private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, Result result) {
+ calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED, result);
- double cpuPowerMaUs = 0;
- for (int cluster = 0; cluster < numClusters; cluster++) {
- final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
- for (int speed = 0; speed < speedsForCluster; speed++) {
- final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
- final double cpuSpeedStepPower = timeUs *
- mProfile.getAveragePowerForCpuCore(cluster, speed);
- if (DEBUG) {
- Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
- + speed + " timeUs=" + timeUs + " power="
- + formatCharge(cpuSpeedStepPower / MICROSEC_IN_HR));
- }
- cpuPowerMaUs += cpuSpeedStepPower;
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, result.powerMah)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, result.durationMs)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND,
+ result.durationFgMs)
+ .setPackageWithHighestDrain(result.packageWithHighestDrain);
+ }
+
+ @Override
+ public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
+ Result result = new Result();
+ for (int i = sippers.size() - 1; i >= 0; i--) {
+ final BatterySipper app = sippers.get(i);
+ if (app.drainType == BatterySipper.DrainType.APP) {
+ calculateApp(app, app.uidObj, statsType, result);
}
}
- cpuPowerMaUs += u.getCpuActiveTime() * 1000 * mProfile.getAveragePower(
- PowerProfile.POWER_CPU_ACTIVE);
+ }
+
+ private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) {
+ calculatePowerAndDuration(u, statsType, result);
+
+ app.cpuPowerMah = result.powerMah;
+ app.cpuTimeMs = result.durationMs;
+ app.cpuFgTimeMs = result.durationFgMs;
+ app.packageWithHighestDrain = result.packageWithHighestDrain;
+ }
+
+ private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType, Result result) {
+ long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
+
+ // Constant battery drain when CPU is active
+ double powerMah = mCpuActivePowerEstimator.calculatePower(u.getCpuActiveTime());
+
+ // Additional per-cluster battery drain
long[] cpuClusterTimes = u.getCpuClusterTimes();
if (cpuClusterTimes != null) {
- if (cpuClusterTimes.length == numClusters) {
- for (int i = 0; i < numClusters; i++) {
- double power =
- cpuClusterTimes[i] * 1000 * mProfile.getAveragePowerForCpuCluster(i);
- cpuPowerMaUs += power;
+ if (cpuClusterTimes.length == mNumCpuClusters) {
+ for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+ double power = mPerClusterPowerEstimators[cluster]
+ .calculatePower(cpuClusterTimes[cluster]);
+ powerMah += power;
if (DEBUG) {
- Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + i + " clusterTimeUs="
- + cpuClusterTimes[i] + " power="
- + formatCharge(power / MICROSEC_IN_HR));
+ Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
+ + " clusterTimeMs=" + cpuClusterTimes[cluster]
+ + " power=" + formatCharge(power));
}
}
} else {
Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # "
- + numClusters + " actual # " + cpuClusterTimes.length);
+ + mNumCpuClusters + " actual # " + cpuClusterTimes.length);
}
}
- final double cpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
- if (DEBUG && (cpuTimeMs != 0 || cpuPowerMah != 0)) {
- Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + cpuTimeMs + " ms power="
- + formatCharge(cpuPowerMah));
+ // Additional per-frequency battery drain
+ for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+ final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length;
+ for (int speed = 0; speed < speedsForCluster; speed++) {
+ final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
+ final double power =
+ mPerCpuFreqPowerEstimators[cluster][speed].calculatePower(timeUs / 1000);
+ if (DEBUG) {
+ Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
+ + speed + " timeUs=" + timeUs + " power="
+ + formatCharge(power));
+ }
+ powerMah += power;
+ }
+ }
+
+ if (DEBUG && (durationMs != 0 || powerMah != 0)) {
+ Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + durationMs + " ms power="
+ + formatCharge(powerMah));
}
// Keep track of the package with highest drain.
double highestDrain = 0;
String packageWithHighestDrain = null;
- long cpuFgTimeMs = 0;
+ long durationFgMs = 0;
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
final int processStatsCount = processStats.size();
for (int i = 0; i < processStatsCount; i++) {
final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
final String processName = processStats.keyAt(i);
- cpuFgTimeMs += ps.getForegroundTime(statsType);
+ durationFgMs += ps.getForegroundTime(statsType);
final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
+ ps.getForegroundTime(statsType);
@@ -107,20 +192,19 @@
}
}
-
// Ensure that the CPU times make sense.
- if (cpuFgTimeMs > cpuTimeMs) {
- if (DEBUG && cpuFgTimeMs > cpuTimeMs + 10000) {
+ if (durationFgMs > durationMs) {
+ if (DEBUG && durationFgMs > durationMs + 10000) {
Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
}
// Statistics may not have been gathered yet.
- cpuTimeMs = cpuFgTimeMs;
+ durationMs = durationFgMs;
}
- app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, cpuPowerMah)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, cpuTimeMs)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, cpuFgTimeMs)
- .setPackageWithHighestDrain(packageWithHighestDrain);
+ result.durationMs = durationMs;
+ result.durationFgMs = durationFgMs;
+ result.powerMah = powerMah;
+ result.packageWithHighestDrain = packageWithHighestDrain;
}
}
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index 9ea934a..df25cda 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -45,8 +45,7 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index dcc8a15..4a4991b 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -49,8 +49,7 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
BatteryStats.STATS_SINCE_CHARGED);
if (mPowerMah != 0) {
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index df46058..21dcce9 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -26,8 +26,7 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index e3bd64d..22001d4 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -85,8 +85,7 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
PowerAndDuration total = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 6ab8c90..362ca07 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -39,8 +39,7 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 05fcc70..7c45cc0 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -67,17 +67,10 @@
* @param batteryStats The recorded battery stats.
* @param rawRealtimeUs The raw system realtime in microseconds.
* @param rawUptimeUs The raw system uptime in microseconds.
- * @param statsType The type of stats. As of {@link android.os.Build.VERSION_CODES#Q}, this
- * can only be {@link BatteryStats#STATS_SINCE_CHARGED}, since
- * {@link BatteryStats#STATS_CURRENT} and
- * {@link BatteryStats#STATS_SINCE_UNPLUGGED} are deprecated.
- * @param asUsers An array of users for which the attribution is requested. It may
- * contain {@link UserHandle#USER_ALL} to indicate that the attribution
- * should be performed for all users.
+ * @param query The query parameters for the calculator.
*/
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
@@ -99,19 +92,6 @@
*/
protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
-
- // TODO(b/175156498): Temporary code during the transition from BatterySippers to
- // BatteryConsumers.
- UidBatteryConsumer.Builder builder = new UidBatteryConsumer.Builder(0, 0, u);
- calculateApp(builder, u, rawRealtimeUs, rawUptimeUs, BatteryUsageStatsQuery.DEFAULT);
- final UidBatteryConsumer uidBatteryConsumer = builder.build();
- app.cpuPowerMah = uidBatteryConsumer.getConsumedPower(
- UidBatteryConsumer.POWER_COMPONENT_CPU);
- app.cpuTimeMs = uidBatteryConsumer.getUsageDurationMillis(
- UidBatteryConsumer.TIME_COMPONENT_CPU);
- app.cpuFgTimeMs = uidBatteryConsumer.getUsageDurationMillis(
- UidBatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND);
- app.packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
}
/**
@@ -163,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 c86c795..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));
@@ -51,19 +59,41 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
- 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);
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ 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.
}
/**
@@ -72,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 =
@@ -132,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);
@@ -171,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/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
index fbad75e..3ed59f1 100644
--- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -110,4 +110,24 @@
return mDeltaCpuThreadTimes;
}
+
+ /** Returns CPU times, per thread group, since tracking started. */
+ @Nullable
+ public SystemServiceCpuThreadTimes readAbsolute() {
+ final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
+ final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
+ mKernelCpuThreadReader.getProcessCpuUsage();
+ if (processCpuUsage == null) {
+ return null;
+ }
+ final SystemServiceCpuThreadTimes result = new SystemServiceCpuThreadTimes();
+ result.threadCpuTimesUs = new long[numCpuFrequencies];
+ result.binderThreadCpuTimesUs = new long[numCpuFrequencies];
+ for (int i = 0; i < numCpuFrequencies; ++i) {
+ result.threadCpuTimesUs[i] = processCpuUsage.threadCpuTimesMillis[i] * 1_000;
+ result.binderThreadCpuTimesUs[i] =
+ processCpuUsage.selectedThreadCpuTimesMillis[i] * 1_000;
+ }
+ return result;
+ }
}
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index 55fc1bb..955f6af 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -51,10 +51,9 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
- SparseArray<UserHandle> asUsers) {
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
calculateSystemServicePower(batteryStats);
- super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query, asUsers);
+ super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
}
@Override
diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java
index 53f8515..8e80286 100644
--- a/core/java/com/android/internal/os/UserPowerCalculator.java
+++ b/core/java/com/android/internal/os/UserPowerCalculator.java
@@ -17,10 +17,15 @@
package com.android.internal.os;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
import android.os.Process;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.SparseArray;
+import com.android.internal.util.ArrayUtils;
+
import java.util.List;
/**
@@ -29,6 +34,33 @@
public class UserPowerCalculator extends PowerCalculator {
@Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final int[] userIds = query.getUserIds();
+ if (ArrayUtils.contains(userIds, UserHandle.USER_ALL)) {
+ return;
+ }
+
+ SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ UidBatteryConsumer.Builder uidBuilder = uidBatteryConsumerBuilders.valueAt(i);
+ final int uid = uidBuilder.getUid();
+ if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+ continue;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+ if (!ArrayUtils.contains(userIds, userId)) {
+ uidBuilder.excludeFromBatteryUsageStats();
+ builder.getOrCreateUserBatteryConsumerBuilder(userId)
+ .addUidBatteryConsumer(uidBuilder);
+ }
+ }
+ }
+
+ @Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
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..38ef55c 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;
@@ -247,7 +246,7 @@
}
/**
- * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+ * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
*/
public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
if (Display.isOnState(screenState)) {
@@ -450,7 +449,8 @@
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. */
+ 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/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
new file mode 100644
index 0000000..6cc5a4a
--- /dev/null
+++ b/core/java/com/android/internal/widget/CallLayout.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.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.app.Person;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * A custom-built layout for the Notification.CallStyle.
+ */
+@RemoteViews.RemoteView
+public class CallLayout extends FrameLayout {
+ private final PeopleHelper mPeopleHelper = new PeopleHelper();
+
+ private int mLayoutColor;
+ private Icon mLargeIcon;
+ private Person mUser;
+
+ private CachingIconView mConversationIconView;
+ private CachingIconView mIcon;
+ private CachingIconView mConversationIconBadgeBg;
+ private TextView mConversationText;
+
+ public CallLayout(@NonNull Context context) {
+ super(context);
+ }
+
+ public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mPeopleHelper.init(getContext());
+ mConversationText = findViewById(R.id.conversation_text);
+ mConversationIconView = findViewById(R.id.conversation_icon);
+ mIcon = findViewById(R.id.icon);
+ mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);
+
+ // When the small icon is gone, hide the rest of the badge
+ mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
+ mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+ });
+ }
+
+ private void updateCallLayout() {
+ CharSequence callerName = "";
+ String symbol = "";
+ Icon icon = null;
+ if (mUser != null) {
+ icon = mUser.getIcon();
+ callerName = mUser.getName();
+ symbol = mPeopleHelper.findNamePrefix(callerName, "");
+ }
+ if (icon == null) {
+ icon = mLargeIcon;
+ }
+ if (icon == null) {
+ icon = mPeopleHelper.createAvatarSymbol(callerName, symbol, mLayoutColor);
+ }
+ // TODO(b/179178086): crop/clip the icon to a circle?
+ mConversationIconView.setImageIcon(icon);
+ mConversationText.setText(callerName);
+ }
+
+ @RemotableViewMethod
+ public void setLayoutColor(int color) {
+ mLayoutColor = color;
+ }
+
+ /**
+ * @param color the color of the notification background
+ */
+ @RemotableViewMethod
+ public void setNotificationBackgroundColor(int color) {
+ mConversationIconBadgeBg.setImageTintList(ColorStateList.valueOf(color));
+ }
+
+ @RemotableViewMethod
+ public void setLargeIcon(Icon largeIcon) {
+ mLargeIcon = largeIcon;
+ }
+
+ /**
+ * Set the notification extras so that this layout has access
+ */
+ @RemotableViewMethod
+ public void setData(Bundle extras) {
+ setUser(extras.getParcelable(Notification.EXTRA_CALL_PERSON));
+ updateCallLayout();
+ }
+
+ private void setUser(Person user) {
+ mUser = user;
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 40e671f..1b1e0bf 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -18,8 +18,6 @@
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -34,10 +32,7 @@
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
@@ -68,14 +63,12 @@
import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
-import com.android.internal.util.ContrastColorUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
-import java.util.regex.Pattern;
/**
* A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
@@ -85,16 +78,6 @@
public class ConversationLayout extends FrameLayout
implements ImageMessageConsumer, IMessagingLayout {
- private static final float COLOR_SHIFT_AMOUNT = 60;
- /**
- * Pattern for filter some ignorable characters.
- * p{Z} for any kind of whitespace or invisible separator.
- * p{C} for any kind of punctuation character.
- */
- private static final Pattern IGNORABLE_CHAR_PATTERN
- = Pattern.compile("[\\p{C}\\p{Z}]");
- private static final Pattern SPECIAL_CHAR_PATTERN
- = Pattern.compile ("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
private static final Consumer<MessagingMessage> REMOVE_MESSAGE
= MessagingMessage::removeMessage;
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
@@ -106,6 +89,7 @@
public static final int IMPORTANCE_ANIM_GROW_DURATION = 250;
public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200;
public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25;
+ private final PeopleHelper mPeopleHelper = new PeopleHelper();
private List<MessagingMessage> mMessages = new ArrayList<>();
private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
private MessagingLinearLayout mMessagingLinearLayout;
@@ -114,9 +98,6 @@
private int mLayoutColor;
private int mSenderTextColor;
private int mMessageTextColor;
- private int mAvatarSize;
- private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private Paint mTextPaint = new Paint();
private Icon mAvatarReplacement;
private boolean mIsOneToOne;
private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
@@ -196,6 +177,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mPeopleHelper.init(getContext());
mMessagingLinearLayout = findViewById(R.id.notification_messaging);
mActions = findViewById(R.id.actions);
mImageMessageContainer = findViewById(R.id.conversation_image_message_container);
@@ -205,9 +187,6 @@
int size = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
mMessagingClipRect = new Rect(0, 0, size, size);
setMessagingClippingDisabled(false);
- mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
- mTextPaint.setTextAlign(Paint.Align.CENTER);
- mTextPaint.setAntiAlias(true);
mConversationIconView = findViewById(R.id.conversation_icon);
mConversationIconContainer = findViewById(R.id.conversation_icon_container);
mIcon = findViewById(R.id.icon);
@@ -250,15 +229,15 @@
});
// When the small icon is gone, hide the rest of the badge
mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
- animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
- animateViewForceHidden(mImportanceRingView, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
});
// When the conversation icon is gone, hide the whole badge
mConversationIconView.setOnForceHiddenChangedListener((forceHidden) -> {
- animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
- animateViewForceHidden(mImportanceRingView, forceHidden);
- animateViewForceHidden(mIcon, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
+ mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
});
mConversationText = findViewById(R.id.conversation_text);
mExpandButtonContainer = findViewById(R.id.expand_button_container);
@@ -317,28 +296,6 @@
R.dimen.notification_header_separating_margin);
}
- private void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
- boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
- if (forceHidden == nowForceHidden) {
- // We are either already forceHidden or will be
- return;
- }
- view.animate().cancel();
- view.setWillBeForceHidden(forceHidden);
- view.animate()
- .scaleX(forceHidden ? 0.5f : 1.0f)
- .scaleY(forceHidden ? 0.5f : 1.0f)
- .alpha(forceHidden ? 0.0f : 1.0f)
- .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
- .setDuration(160);
- if (view.getVisibility() != VISIBLE) {
- view.setForceHidden(forceHidden);
- } else {
- view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
- }
- view.animate().start();
- }
-
@RemotableViewMethod
public void setAvatarReplacement(Icon icon) {
mAvatarReplacement = icon;
@@ -561,7 +518,8 @@
if (mConversationIcon == null) {
Icon avatarIcon = messagingGroup.getAvatarIcon();
if (avatarIcon == null) {
- avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor);
+ avatarIcon = mPeopleHelper.createAvatarSymbol(conversationText, "",
+ mLayoutColor);
}
mConversationIcon = avatarIcon;
}
@@ -674,11 +632,11 @@
}
}
if (lastIcon == null) {
- lastIcon = createAvatarSymbol(" ", "", mLayoutColor);
+ lastIcon = mPeopleHelper.createAvatarSymbol(" ", "", mLayoutColor);
}
bottomView.setImageIcon(lastIcon);
if (secondLastIcon == null) {
- secondLastIcon = createAvatarSymbol("", "", mLayoutColor);
+ secondLastIcon = mPeopleHelper.createAvatarSymbol("", "", mLayoutColor);
}
topView.setImageIcon(secondLastIcon);
}
@@ -838,8 +796,10 @@
}
private void updateTitleAndNamesDisplay() {
+ // Map of unique names to their prefix
ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
- ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+ // Map of single-character string prefix to the only name which uses it, or null if multiple
+ ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
for (int i = 0; i < mGroups.size(); i++) {
MessagingGroup group = mGroups.get(i);
CharSequence senderName = group.getSenderName();
@@ -847,22 +807,22 @@
continue;
}
if (!uniqueNames.containsKey(senderName)) {
- // Only use visible characters to get uniqueNames
- String pureSenderName = IGNORABLE_CHAR_PATTERN
- .matcher(senderName).replaceAll("" /* replacement */);
- char c = pureSenderName.charAt(0);
- if (uniqueCharacters.containsKey(c)) {
+ String charPrefix = mPeopleHelper.findNamePrefix(senderName, null);
+ if (charPrefix == null) {
+ continue;
+ }
+ if (uniqueCharacters.containsKey(charPrefix)) {
// this character was already used, lets make it more unique. We first need to
// resolve the existing character if it exists
- CharSequence existingName = uniqueCharacters.get(c);
+ CharSequence existingName = uniqueCharacters.get(charPrefix);
if (existingName != null) {
- uniqueNames.put(existingName, findNameSplit((String) existingName));
- uniqueCharacters.put(c, null);
+ uniqueNames.put(existingName, mPeopleHelper.findNameSplit(existingName));
+ uniqueCharacters.put(charPrefix, null);
}
- uniqueNames.put(senderName, findNameSplit((String) senderName));
+ uniqueNames.put(senderName, mPeopleHelper.findNameSplit(senderName));
} else {
- uniqueNames.put(senderName, Character.toString(c));
- uniqueCharacters.put(c, pureSenderName);
+ uniqueNames.put(senderName, charPrefix);
+ uniqueCharacters.put(charPrefix, senderName);
}
}
}
@@ -898,8 +858,8 @@
} else {
Icon cachedIcon = cachedAvatars.get(senderName);
if (cachedIcon == null) {
- cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
- mLayoutColor);
+ cachedIcon = mPeopleHelper.createAvatarSymbol(senderName,
+ uniqueNames.get(senderName), mLayoutColor);
cachedAvatars.put(senderName, cachedIcon);
}
group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
@@ -908,49 +868,6 @@
}
}
- private Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
- if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol) ||
- SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
- Icon avatarIcon = Icon.createWithResource(getContext(),
- R.drawable.messaging_user);
- avatarIcon.setTint(findColor(senderName, layoutColor));
- return avatarIcon;
- } else {
- Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- float radius = mAvatarSize / 2.0f;
- int color = findColor(senderName, layoutColor);
- mPaint.setColor(color);
- canvas.drawCircle(radius, radius, radius, mPaint);
- boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
- mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
- mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
- int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
- canvas.drawText(symbol, radius, yPos, mTextPaint);
- return Icon.createWithBitmap(bitmap);
- }
- }
-
- private int findColor(CharSequence senderName, int layoutColor) {
- double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
- float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
-
- // we need to offset the range if the luminance is too close to the borders
- shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
- shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
- return ContrastColorUtil.getShiftedColor(layoutColor,
- (int) (shift * COLOR_SHIFT_AMOUNT));
- }
-
- private String findNameSplit(String existingName) {
- String[] split = existingName.split(" ");
- if (split.length > 1) {
- return Character.toString(split[0].charAt(0))
- + Character.toString(split[1].charAt(0));
- }
- return existingName.substring(0, 1);
- }
-
@RemotableViewMethod
public void setLayoutColor(int color) {
mLayoutColor = color;
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 5213746..058a921 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,17 +16,24 @@
package com.android.internal.widget;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
+import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.LinearLayout;
import android.widget.RemoteViews;
+import com.android.internal.R;
+
/**
* A button implementation for the emphasized notification style.
*
@@ -37,6 +44,7 @@
private final RippleDrawable mRipple;
private final int mStrokeWidth;
private final int mStrokeColor;
+ private boolean mPriority;
public EmphasizedNotificationButton(Context context) {
this(context, null);
@@ -80,4 +88,57 @@
inner.setStroke(hasStroke ? mStrokeWidth : 0, mStrokeColor);
invalidate();
}
+
+ /**
+ * Sets an image icon which will have its size constrained and will be set to the same color as
+ * the text. Must be called after {@link #setTextColor(int)} for the latter to work.
+ */
+ @RemotableViewMethod(asyncImpl = "setImageIconAsync")
+ public void setImageIcon(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ setImageDrawable(drawable);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable setImageIconAsync(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ return () -> setImageDrawable(drawable);
+ }
+
+ private void setImageDrawable(Drawable drawable) {
+ if (drawable != null) {
+ drawable.mutate();
+ drawable.setTintList(getTextColors());
+ drawable.setTintBlendMode(BlendMode.SRC_IN);
+ int iconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_icon_drawable_size);
+ drawable.setBounds(0, 0, iconSize, iconSize);
+ }
+ setCompoundDrawablesRelative(drawable, null, null, null);
+ }
+
+ /**
+ * Changes the LayoutParams.width to WRAP_CONTENT, with the argument representing if this view
+ * is a priority over its peers (which affects weight).
+ */
+ @RemotableViewMethod
+ public void setWrapModePriority(boolean priority) {
+ mPriority = priority;
+ ViewGroup.LayoutParams layoutParams = getLayoutParams();
+ layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ if (layoutParams instanceof LinearLayout.LayoutParams) {
+ ((LinearLayout.LayoutParams) layoutParams).weight = 0;
+ }
+ setLayoutParams(layoutParams);
+ }
+
+ /**
+ * Sizing this button is a priority compared with its peers.
+ */
+ public boolean isPriority() {
+ return mPriority;
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index c7ea781..8e6497b 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import android.annotation.DimenRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.RippleDrawable;
@@ -41,13 +42,16 @@
private final int mGravity;
private int mTotalWidth = 0;
+ private int mExtraStartPadding = 0;
private ArrayList<Pair<Integer, TextView>> mMeasureOrderTextViews = new ArrayList<>();
private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
private boolean mEmphasizedMode;
+ private boolean mPrioritizedWrapMode;
private int mDefaultPaddingBottom;
private int mDefaultPaddingTop;
private int mEmphasizedHeight;
private int mRegularHeight;
+ @DimenRes private int mCollapsibleIndentDimen;
public NotificationActionListLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -68,7 +72,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mEmphasizedMode) {
+ if (mEmphasizedMode && !mPrioritizedWrapMode) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
@@ -151,7 +155,15 @@
measuredChildren++;
}
- mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft;
+ int collapsibleIndent = mCollapsibleIndentDimen == 0 ? 0
+ : getResources().getDimensionPixelOffset(mCollapsibleIndentDimen);
+ if (innerWidth - usedWidth > collapsibleIndent) {
+ mExtraStartPadding = collapsibleIndent;
+ } else {
+ mExtraStartPadding = 0;
+ }
+
+ mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
@@ -163,7 +175,11 @@
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View c = getChildAt(i);
- if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
+ if (c instanceof EmphasizedNotificationButton
+ && ((EmphasizedNotificationButton) c).isPriority()) {
+ // add with 0 length to ensure that this view is measured before others.
+ mMeasureOrderTextViews.add(Pair.create(0, (TextView) c));
+ } else if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
mMeasureOrderTextViews.add(Pair.create(((TextView) c).getText().length(),
(TextView)c));
} else {
@@ -197,7 +213,7 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (mEmphasizedMode) {
+ if (mEmphasizedMode && !mPrioritizedWrapMode) {
super.onLayout(changed, left, top, right, bottom);
return;
}
@@ -214,6 +230,9 @@
int absoluteGravity = Gravity.getAbsoluteGravity(Gravity.START, getLayoutDirection());
if (absoluteGravity == Gravity.RIGHT) {
childLeft += right - left - mTotalWidth;
+ } else {
+ // Put the extra start padding (if any) on the left when LTR
+ childLeft += mExtraStartPadding;
}
}
@@ -274,6 +293,26 @@
}
/**
+ * When used with emphasizedMode, changes the button sizing behavior to prioritize certain
+ * buttons (which are system generated) to not scrunch, and leave the remaining space for
+ * custom actions.
+ */
+ @RemotableViewMethod
+ public void setPrioritizedWrapMode(boolean prioritizedWrapMode) {
+ mPrioritizedWrapMode = prioritizedWrapMode;
+ }
+
+ /**
+ * When buttons are in wrap mode, this is a padding that will be applied at the start of the
+ * layout of the actions, but only when those actions would fit with the entire padding
+ * visible. Otherwise, this padding will be omitted entirely.
+ */
+ @RemotableViewMethod
+ public void setCollapsibleIndentDimen(@DimenRes int collapsibleIndentDimen) {
+ mCollapsibleIndentDimen = collapsibleIndentDimen;
+ }
+
+ /**
* Set whether the list is in a mode where some actions are emphasized. This will trigger an
* equal measuring where all actions are full height and change a few parameters like
* the padding.
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
new file mode 100644
index 0000000..77f4c8f
--- /dev/null
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -0,0 +1,179 @@
+/*
+ * 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.widget;
+
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.ContrastColorUtil;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class provides some methods used by both the {@link ConversationLayout} and
+ * {@link CallLayout} which both use the visual design originally created for conversations in R.
+ */
+public class PeopleHelper {
+
+ private static final float COLOR_SHIFT_AMOUNT = 60;
+ /**
+ * Pattern for filter some ignorable characters.
+ * p{Z} for any kind of whitespace or invisible separator.
+ * p{C} for any kind of punctuation character.
+ */
+ private static final Pattern IGNORABLE_CHAR_PATTERN = Pattern.compile("[\\p{C}\\p{Z}]");
+ private static final Pattern SPECIAL_CHAR_PATTERN =
+ Pattern.compile("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
+
+ private Context mContext;
+ private int mAvatarSize;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Paint mTextPaint = new Paint();
+
+ /**
+ * Call this when the view is inflated to provide a context and initialize the helper
+ */
+ public void init(Context context) {
+ mContext = context;
+ mAvatarSize = context.getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setAntiAlias(true);
+ }
+
+ /**
+ * A utility for animating CachingIconViews away when hidden.
+ */
+ public void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
+ boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
+ if (forceHidden == nowForceHidden) {
+ // We are either already forceHidden or will be
+ return;
+ }
+ view.animate().cancel();
+ view.setWillBeForceHidden(forceHidden);
+ view.animate()
+ .scaleX(forceHidden ? 0.5f : 1.0f)
+ .scaleY(forceHidden ? 0.5f : 1.0f)
+ .alpha(forceHidden ? 0.0f : 1.0f)
+ .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
+ .setDuration(160);
+ if (view.getVisibility() != View.VISIBLE) {
+ view.setForceHidden(forceHidden);
+ } else {
+ view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
+ }
+ view.animate().start();
+ }
+
+ /**
+ * This creates an avatar symbol for the given person or group
+ *
+ * @param name the name of the person or group
+ * @param symbol a pre-chosen symbol for the person or group. See
+ * {@link #findNamePrefix(CharSequence, String)} or
+ * {@link #findNameSplit(CharSequence)}
+ * @param layoutColor the background color of the layout
+ */
+ @NonNull
+ public Icon createAvatarSymbol(@NonNull CharSequence name, @NonNull String symbol,
+ @ColorInt int layoutColor) {
+ if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol)
+ || SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
+ Icon avatarIcon = Icon.createWithResource(mContext, R.drawable.messaging_user);
+ avatarIcon.setTint(findColor(name, layoutColor));
+ return avatarIcon;
+ } else {
+ Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ float radius = mAvatarSize / 2.0f;
+ int color = findColor(name, layoutColor);
+ mPaint.setColor(color);
+ canvas.drawCircle(radius, radius, radius, mPaint);
+ boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
+ mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+ mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
+ int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
+ canvas.drawText(symbol, radius, yPos, mTextPaint);
+ return Icon.createWithBitmap(bitmap);
+ }
+ }
+
+ private int findColor(@NonNull CharSequence senderName, int layoutColor) {
+ double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
+ float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+ // we need to offset the range if the luminance is too close to the borders
+ shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+ shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+ return ContrastColorUtil.getShiftedColor(layoutColor,
+ (int) (shift * COLOR_SHIFT_AMOUNT));
+ }
+
+ /**
+ * Get the name with whitespace and punctuation characters removed
+ */
+ private String getPureName(@NonNull CharSequence name) {
+ return IGNORABLE_CHAR_PATTERN.matcher(name).replaceAll("" /* replacement */);
+ }
+
+ /**
+ * Gets a single character string prefix name for the person or group
+ *
+ * @param name the name of the person or group
+ * @param fallback the string to return if the name has no usable characters
+ */
+ public String findNamePrefix(@NonNull CharSequence name, String fallback) {
+ String pureName = getPureName(name);
+ if (pureName.isEmpty()) {
+ return fallback;
+ }
+ try {
+ return new String(Character.toChars(pureName.codePointAt(0)));
+ } catch (RuntimeException ignore) {
+ return fallback;
+ }
+ }
+
+ /**
+ * Find a 1 or 2 character prefix name for the person or group
+ */
+ public String findNameSplit(@NonNull CharSequence name) {
+ String nameString = name instanceof String ? ((String) name) : name.toString();
+ String[] split = nameString.trim().split("[ ]+");
+ if (split.length > 1) {
+ String first = findNamePrefix(split[0], null);
+ String second = findNamePrefix(split[1], null);
+ if (first != null && second != null) {
+ return first + second;
+ }
+ }
+ return findNamePrefix(name, "");
+ }
+}
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 bb51c57..c64174b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -43,6 +43,7 @@
#include <nativehelper/JNIPlatformHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include "jni.h"
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
#include <dmabufinfo/dmabufinfo.h>
#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
@@ -805,6 +806,16 @@
return heapsSizeKb;
}
+static jlong android_os_Debug_getDmabufTotalExportedKb(JNIEnv* env, jobject clazz) {
+ jlong dmabufTotalSizeKb = -1;
+ uint64_t size;
+
+ if (dmabufinfo::GetDmabufTotalExportedKb(&size)) {
+ dmabufTotalSizeKb = size;
+ }
+ return dmabufTotalSizeKb;
+}
+
static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) {
jlong poolsSizeKb = -1;
uint64_t size;
@@ -816,8 +827,19 @@
return poolsSizeKb;
}
-static jlong android_os_Debug_getIonMappedSizeKb(JNIEnv* env, jobject clazz) {
- jlong ionPss = 0;
+static jlong android_os_Debug_getDmabufHeapPoolsSizeKb(JNIEnv* env, jobject clazz) {
+ jlong poolsSizeKb = -1;
+ uint64_t size;
+
+ if (meminfo::ReadDmabufHeapPoolsSizeKb(&size)) {
+ poolsSizeKb = size;
+ }
+
+ return poolsSizeKb;
+}
+
+static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
+ jlong dmabufPss = 0;
std::vector<dmabufinfo::DmaBuffer> dmabufs;
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
@@ -841,10 +863,10 @@
}
for (const dmabufinfo::DmaBuffer& buf : dmabufs) {
- ionPss += buf.size() / 1024;
+ dmabufPss += buf.size() / 1024;
}
- return ionPss;
+ return dmabufPss;
}
static jlong android_os_Debug_getGpuTotalUsageKb(JNIEnv* env, jobject clazz) {
@@ -922,10 +944,14 @@
(void*)android_os_Debug_getFreeZramKb },
{ "getIonHeapsSizeKb", "()J",
(void*)android_os_Debug_getIonHeapsSizeKb },
+ { "getDmabufTotalExportedKb", "()J",
+ (void*)android_os_Debug_getDmabufTotalExportedKb },
{ "getIonPoolsSizeKb", "()J",
(void*)android_os_Debug_getIonPoolsSizeKb },
- { "getIonMappedSizeKb", "()J",
- (void*)android_os_Debug_getIonMappedSizeKb },
+ { "getDmabufMappedSizeKb", "()J",
+ (void*)android_os_Debug_getDmabufMappedSizeKb },
+ { "getDmabufHeapPoolsSizeKb", "()J",
+ (void*)android_os_Debug_getDmabufHeapPoolsSizeKb },
{ "getGpuTotalUsageKb", "()J",
(void*)android_os_Debug_getGpuTotalUsageKb },
{ "isVmapStack", "()Z",
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/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto
index 6794c8c..0041f19 100644
--- a/core/proto/android/net/networkrequest.proto
+++ b/core/proto/android/net/networkrequest.proto
@@ -63,6 +63,9 @@
// higher-scoring network will not go into the background immediately,
// but will linger and go into the background after the linger timeout.
TYPE_BACKGROUND_REQUEST = 5;
+ // Like TRACK_DEFAULT, but tracks the system default network, instead of
+ // the default network of the calling application.
+ TYPE_TRACK_SYSTEM_DEFAULT = 6;
}
// The type of the request. This is only used by the system and is always
// NONE elsewhere.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 827bf7b..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
@@ -2698,11 +2706,11 @@
The app can check whether it has this authorization by calling
{@link android.provider.Settings#canDrawOverlays
Settings.canDrawOverlays()}.
- <p>Protection level: signature|appop|installer|recents|appPredictor|pre23|development -->
+ <p>Protection level: signature|appop|installer|appPredictor|pre23|development -->
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
- android:protectionLevel="signature|appop|installer|recents|appPredictor|pre23|development" />
+ android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" />
<!-- @SystemApi @hide Allows an application to create windows using the type
{@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
@@ -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. -->
@@ -5447,6 +5460,11 @@
<permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
android:protectionLevel="signature" />
+ <!-- Allows managing the Game Mode
+ @hide Used internally. -->
+ <permission android:name="android.permission.MANAGE_GAME_MODE"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/color/btn_leanback_color.xml b/core/res/res/color/btn_leanback_color.xml
new file mode 100644
index 0000000..012df60
--- /dev/null
+++ b/core/res/res/color/btn_leanback_color.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true"
+ android:color="@color/btn_leanback_focused" />
+ <item android:state_enabled="false"
+ android:color="@android:color/transparent" />
+ <item android:color="@color/btn_leanback_unfocused" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/btn_leanback_text_color.xml b/core/res/res/color/btn_leanback_text_color.xml
new file mode 100644
index 0000000..df0df94
--- /dev/null
+++ b/core/res/res/color/btn_leanback_text_color.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/btn_text_leanback_focused" android:state_focused="true"/>
+ <item android:color="@color/btn_text_leanback_unfocused"/>
+</selector>
diff --git a/core/res/res/drawable/btn_leanback.xml b/core/res/res/drawable/btn_leanback.xml
new file mode 100644
index 0000000..9a63986
--- /dev/null
+++ b/core/res/res/drawable/btn_leanback.xml
@@ -0,0 +1,20 @@
+<?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="@color/btn_leanback_color"/>
+ <corners android:radius="@dimen/leanback_button_radius"/>
+</shape>
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 1a574fe..ad68054 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -23,7 +23,7 @@
<ripple android:color="?attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
- <corners android:radius="?attr/buttonCornerRadius" />
+ <corners android:radius="@dimen/notification_action_button_radius" />
<padding android:left="@dimen/button_padding_horizontal_material"
android:top="@dimen/button_padding_vertical_material"
android:right="@dimen/button_padding_horizontal_material"
diff --git a/core/res/res/drawable/ic_call_answer.xml b/core/res/res/drawable/ic_call_answer.xml
new file mode 100644
index 0000000..77c0ad1
--- /dev/null
+++ b/core/res/res/drawable/ic_call_answer.xml
@@ -0,0 +1,34 @@
+<!--
+ 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20"
+ android:tint="?android:attr/colorControlNormal"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.0168,3.3333L6.5751,5.575L4.6668,7.4916C3.8751,5.7166 3.6001,4.1916
+ 3.4834,3.3333H6.0168ZM14.4251,13.375L16.6668,13.9416V16.5166C15.8084,16.4 14.2668,16.125
+ 12.4834,15.325L14.4251,13.375ZM6.3418,1.6666H2.5668C2.0918,1.6666 1.7001,2.0666
+ 1.7334,2.5416C2.4834,12.875 11.7668,18.275 17.5168,18.275C17.9668,18.275 18.3334,17.9
+ 18.3334,17.4416V13.6166C18.3334,13.0416 17.9418,12.5416
+ 17.3834,12.4083L14.5918,11.7083C14.2251,11.6166 13.7584,11.6833
+ 13.4084,12.0333L10.9251,14.5166C8.6751,13.1833 6.7918,11.3
+ 5.4668,9.0416L7.9168,6.5916C8.2251,6.2833 8.3501,5.8333
+ 8.2418,5.4083L7.5584,2.6166C7.4168,2.0583 6.9168,1.6666 6.3418,1.6666Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_call_decline.xml b/core/res/res/drawable/ic_call_decline.xml
new file mode 100644
index 0000000..a5ee8f4
--- /dev/null
+++ b/core/res/res/drawable/ic_call_decline.xml
@@ -0,0 +1,36 @@
+<!--
+ 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20"
+ android:tint="?android:attr/colorControlNormal"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.2834,9.3083V10.7833L4.1084,11.4916L3.1334,10.5166C3.8084,10.0416
+ 4.5251,9.6333 5.2834,9.3083ZM14.75,9.325C15.4917,9.65 16.2084,10.05
+ 16.875,10.525L15.925,11.475L14.75,10.7666V9.325ZM9.975,6.6666C6.8584,6.6666 3.725,7.7333
+ 1.1751,9.9416C0.8084,10.2583 0.9667,10.7166 1.1501,10.9L3.2917,13.0416C3.4917,13.2333
+ 3.7417,13.3333 4.0001,13.3333C4.175,13.3333 4.35,13.2833
+ 4.5084,13.1916L6.4667,12.0166C6.725,11.8583 6.95,11.5583 6.95,11.1666V8.3833C7.95,8.125
+ 8.975,8 10.0084,8C11.0417,8 12.075,8.1333 13.0834,8.3916V11.1416C13.0834,11.4916
+ 13.2667,11.8166 13.5667,11.9916L15.525,13.1666C15.6834,13.2583 15.8584,13.3083
+ 16.0334,13.3083C16.2917,13.3083 16.5417,13.2083
+ 16.7334,13.0166L18.85,10.9C19.1167,10.6333 19.1167,10.1916 18.825,9.9416C16.3334,7.7833
+ 13.1667,6.6666 9.975,6.6666Z"/>
+</vector>
diff --git a/core/res/res/layout/alert_dialog_button_bar_leanback.xml b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
new file mode 100644
index 0000000..ea94af6
--- /dev/null
+++ b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
@@ -0,0 +1,59 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:scrollIndicators="top|bottom"
+ android:fillViewport="true"
+ style="?attr/buttonBarStyle">
+ <com.android.internal.widget.ButtonBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="locale"
+ android:orientation="horizontal"
+ android:gravity="start">
+
+ <Button
+ android:id="@+id/button1"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:id="@+id/button2"
+ style="?attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+
+ <Button
+ android:id="@+id/button3"
+ style="?attr/buttonBarNeutralButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </com.android.internal.widget.ButtonBarLayout>
+</ScrollView>
diff --git a/core/res/res/layout/alert_dialog_leanback.xml b/core/res/res/layout/alert_dialog_leanback.xml
index 848015c..091613f 100644
--- a/core/res/res/layout/alert_dialog_leanback.xml
+++ b/core/res/res/layout/alert_dialog_leanback.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2014 The Android Open Source Project
+ 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.
@@ -15,108 +15,70 @@
limitations under the License.
-->
-<LinearLayout
+<com.android.internal.widget.AlertDialogLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@drawable/dialog_background_material"
- android:translationZ="@dimen/floating_window_z"
- android:layout_marginLeft="@dimen/leanback_alert_dialog_horizontal_margin"
- android:layout_marginTop="@dimen/leanback_alert_dialog_vertical_margin"
- android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin"
- android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin">
+ android:gravity="start|top"
+ android:orientation="vertical">
- <LinearLayout android:id="@+id/topPanel"
+ <include layout="@layout/alert_dialog_title_material" />
+
+ <FrameLayout
+ android:id="@+id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
- <LinearLayout android:id="@+id/title_template"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical|start"
- android:paddingStart="16dip"
- android:paddingEnd="16dip"
- android:paddingTop="16dip">
- <ImageView android:id="@+id/icon"
- android:layout_width="32dip"
- android:layout_height="32dip"
- android:layout_marginEnd="8dip"
- android:scaleType="fitCenter"
- android:src="@null" />
- <TextView android:id="@+id/alertTitle"
- style="?attr/windowTitleStyle"
- android:singleLine="true"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="viewStart" />
- </LinearLayout>
- <!-- If the client uses a customTitle, it will be added here. -->
- </LinearLayout>
+ android:minHeight="48dp">
- <LinearLayout android:id="@+id/contentPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical"
- android:minHeight="64dp">
- <ScrollView android:id="@+id/scrollView"
+ <ScrollView
+ android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false">
- <TextView android:id="@+id/message"
- style="?attr/textAppearanceMedium"
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="16dip"
- android:paddingEnd="16dip"
- android:paddingTop="16dip" />
- </ScrollView>
- </LinearLayout>
+ android:orientation="vertical">
- <FrameLayout android:id="@+id/customPanel"
+ <Space
+ android:id="@+id/textSpacerNoTitle"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialog_padding_top_material" />
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ style="@style/TextAppearance.Material.Subhead" />
+
+ <Space
+ android:id="@+id/textSpacerNoButtons"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialog_padding_top_material" />
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/customPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:minHeight="64dp">
- <FrameLayout android:id="@+android:id/custom"
+ android:minHeight="48dp">
+
+ <FrameLayout
+ android:id="@+id/custom"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
- <LinearLayout android:id="@+id/buttonPanel"
+ <include
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- android:orientation="vertical"
- android:gravity="end"
- android:padding="16dip">
- <LinearLayout
- style="?attr/buttonBarStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layoutDirection="locale">
- <Button android:id="@+id/button3"
- style="?attr/buttonBarButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height" />
- <Button android:id="@+id/button2"
- style="?attr/buttonBarButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height" />
- <Button android:id="@+id/button1"
- style="?attr/buttonBarButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height" />
- </LinearLayout>
- </LinearLayout>
-</LinearLayout>
+ layout="@layout/alert_dialog_button_bar_leanback" />
+</com.android.internal.widget.AlertDialogLayout>
diff --git a/core/res/res/layout/notification_material_action_emphasized.xml b/core/res/res/layout/notification_material_action_emphasized.xml
index a6b7b38..cd1f1ab 100644
--- a/core/res/res/layout/notification_material_action_emphasized.xml
+++ b/core/res/res/layout/notification_material_action_emphasized.xml
@@ -22,6 +22,7 @@
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_weight="1"
+ android:drawablePadding="6dp"
android:gravity="center"
android:textColor="@color/notification_default_color"
android:singleLine="true"
diff --git a/core/res/res/layout/notification_template_conversation_header.xml b/core/res/res/layout/notification_template_conversation_header.xml
new file mode 100644
index 0000000..b018676
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_header.xml
@@ -0,0 +1,166 @@
+<?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:id="@+id/conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="16dp"
+ >
+
+ <TextView
+ android:id="@+id/conversation_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ />
+
+ <TextView
+ android:id="@+id/app_name_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:layout_gravity="center"
+ android:paddingTop="1sp"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <!-- App Name -->
+ <com.android.internal.widget.ObservableTextView
+ android:id="@+id/app_name_text"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:paddingTop="1sp"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <TextView
+ android:id="@+id/time_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:layout_gravity="center"
+ android:paddingTop="1sp"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <DateTimeView
+ android:id="@+id/time"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:paddingTop="1sp"
+ android:showRelative="true"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <ViewStub
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:layout="@layout/notification_template_part_chronometer"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/verification_icon"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/notification_alerted_content_description"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_notifications_alerted"
+ android:visibility="gone"
+ />
+
+ <TextView
+ android:id="@+id/verification_text"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+ android:paddingTop="1sp"
+ android:showRelative="true"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <ImageButton
+ android:id="@+id/feedback"
+ android:layout_width="@dimen/notification_feedback_size"
+ android:layout_height="@dimen/notification_feedback_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:contentDescription="@string/notification_feedback_indicator"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_feedback_indicator"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/profile_badge"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_work_profile_content_description"
+ />
+
+ <ImageView
+ android:id="@+id/alerted_icon"
+ android:layout_width="@dimen/notification_alerted_size"
+ android:layout_height="@dimen/notification_alerted_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/notification_alerted_content_description"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_notifications_alerted"
+ android:visibility="gone"
+ />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
new file mode 100644
index 0000000..e9ec7ce
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -0,0 +1,97 @@
+<?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
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/conversation_icon_container"
+ android:layout_width="@dimen/conversation_content_start"
+ android:layout_height="wrap_content"
+ android:gravity="start|top"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:importantForAccessibility="no"
+ >
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_gravity="top|center_horizontal"
+ >
+
+ <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/conversation_avatar_size"
+ android:layout_height="@dimen/conversation_avatar_size"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no"
+ />
+
+ <ViewStub
+ android:layout="@layout/conversation_face_pile_layout"
+ android:layout_width="@dimen/conversation_avatar_size"
+ android:layout_height="@dimen/conversation_avatar_size"
+ android:id="@+id/conversation_face_pile"
+ />
+
+ <FrameLayout
+ android:id="@+id/conversation_icon_badge"
+ android:layout_width="@dimen/conversation_icon_size_badged"
+ android:layout_height="@dimen/conversation_icon_size_badged"
+ android:layout_marginLeft="@dimen/conversation_badge_side_margin"
+ android:layout_marginTop="@dimen/conversation_badge_side_margin"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ >
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon_badge_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:src="@drawable/conversation_badge_background"
+ android:forceHasOverlappingRendering="false"
+ android:scaleType="center"
+ />
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/icon"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="4dp"
+ android:layout_gravity="center"
+ android:forceHasOverlappingRendering="false"
+ />
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon_badge_ring"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:src="@drawable/conversation_badge_ring"
+ android:visibility="gone"
+ android:forceHasOverlappingRendering="false"
+ android:clipToPadding="false"
+ android:scaleType="center"
+ />
+ </FrameLayout>
+ </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
new file mode 100644
index 0000000..471d874
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -0,0 +1,92 @@
+<?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
+ -->
+<com.android.internal.widget.CallLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:tag="call"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+ <include layout="@layout/notification_template_conversation_icon_container" />
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_action_list_height"
+ android:orientation="vertical"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="top"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:orientation="vertical"
+ android:minHeight="68dp"
+ >
+
+ <include
+ layout="@layout/notification_template_conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <include layout="@layout/notification_template_text" />
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ layout="@layout/notification_template_progress"
+ />
+ </LinearLayout>
+
+ <!-- TODO(b/179178086): remove padding from main column when this is visible -->
+ <com.android.internal.widget.NotificationExpandButton
+ android:id="@+id/expand_button"
+ android:layout_width="@dimen/notification_header_expand_icon_size"
+ android:layout_height="@dimen/notification_header_expand_icon_size"
+ android:layout_gravity="top|end"
+ android:contentDescription="@string/expand_button_content_description_collapsed"
+ android:paddingTop="@dimen/notification_expand_button_padding_top"
+ android:scaleType="center"
+ android:visibility="gone"
+ />
+
+ </LinearLayout>
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index f9364d5..f3aa540 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -24,82 +24,7 @@
android:theme="@style/Theme.DeviceDefault.Notification"
>
- <FrameLayout
- android:id="@+id/conversation_icon_container"
- android:layout_width="@dimen/conversation_content_start"
- android:layout_height="wrap_content"
- android:gravity="start|top"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:paddingTop="12dp"
- android:paddingBottom="12dp"
- android:importantForAccessibility="no"
- >
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:layout_gravity="top|center_horizontal"
- >
-
- <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
- <com.android.internal.widget.CachingIconView
- android:id="@+id/conversation_icon"
- android:layout_width="@dimen/conversation_avatar_size"
- android:layout_height="@dimen/conversation_avatar_size"
- android:scaleType="centerCrop"
- android:importantForAccessibility="no"
- />
-
- <ViewStub
- android:layout="@layout/conversation_face_pile_layout"
- android:layout_width="@dimen/conversation_avatar_size"
- android:layout_height="@dimen/conversation_avatar_size"
- android:id="@+id/conversation_face_pile"
- />
-
- <FrameLayout
- android:id="@+id/conversation_icon_badge"
- android:layout_width="@dimen/conversation_icon_size_badged"
- android:layout_height="@dimen/conversation_icon_size_badged"
- android:layout_marginLeft="@dimen/conversation_badge_side_margin"
- android:layout_marginTop="@dimen/conversation_badge_side_margin"
- android:clipChildren="false"
- android:clipToPadding="false"
- >
- <com.android.internal.widget.CachingIconView
- android:id="@+id/conversation_icon_badge_bg"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:src="@drawable/conversation_badge_background"
- android:forceHasOverlappingRendering="false"
- android:scaleType="center"
- />
- <com.android.internal.widget.CachingIconView
- android:id="@+id/icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="4dp"
- android:layout_gravity="center"
- android:forceHasOverlappingRendering="false"
- />
- <com.android.internal.widget.CachingIconView
- android:id="@+id/conversation_icon_badge_ring"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/conversation_badge_ring"
- android:visibility="gone"
- android:forceHasOverlappingRendering="false"
- android:clipToPadding="false"
- android:scaleType="center"
- />
- </FrameLayout>
- </FrameLayout>
- </FrameLayout>
+ <include layout="@layout/notification_template_conversation_icon_container" />
<!-- Wraps entire "expandable" notification -->
<com.android.internal.widget.RemeasuringLinearLayout
@@ -132,161 +57,14 @@
<!-- Use layout_marginStart instead of paddingStart to work around strange
measurement behavior on lower display densities. -->
- <LinearLayout
- android:id="@+id/conversation_header"
+ <include
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingTop="16dp"
android:layout_marginBottom="2dp"
android:layout_marginStart="@dimen/conversation_content_start"
- >
- <TextView
- android:id="@+id/conversation_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="16sp"
- android:singleLine="true"
- android:layout_weight="1"
- />
-
- <TextView
- android:id="@+id/app_name_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:layout_gravity="center"
- android:paddingTop="1sp"
- android:singleLine="true"
- android:visibility="gone"
/>
- <!-- App Name -->
- <com.android.internal.widget.ObservableTextView
- android:id="@+id/app_name_text"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:paddingTop="1sp"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <TextView
- android:id="@+id/time_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:layout_gravity="center"
- android:paddingTop="1sp"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <DateTimeView
- android:id="@+id/time"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:paddingTop="1sp"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <ImageButton
- android:id="@+id/feedback"
- android:layout_width="@dimen/notification_feedback_size"
- android:layout_height="@dimen/notification_feedback_size"
- android:layout_gravity="center"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:background="?android:selectableItemBackgroundBorderless"
- android:contentDescription="@string/notification_feedback_indicator"
- android:paddingTop="2dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_feedback_indicator"
- android:visibility="gone"
- />
-
- <ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="2dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
-
- <ImageView
- android:id="@+id/alerted_icon"
- android:layout_width="@dimen/notification_alerted_size"
- android:layout_height="@dimen/notification_alerted_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:contentDescription="@string/notification_alerted_content_description"
- android:paddingTop="2dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_notifications_alerted"
- android:visibility="gone"
- />
-
- <LinearLayout
- android:id="@+id/app_ops"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingTop="3dp"
- android:layout_marginStart="2dp"
- android:background="?android:selectableItemBackgroundBorderless"
- android:orientation="horizontal" >
- <ImageView
- android:layout_marginStart="4dp"
- android:id="@+id/camera"
- android:layout_width="?attr/notificationHeaderIconSize"
- android:layout_height="?attr/notificationHeaderIconSize"
- android:src="@drawable/ic_camera"
- android:visibility="gone"
- android:focusable="false"
- android:contentDescription="@string/notification_appops_camera_active"
- />
- <ImageView
- android:id="@+id/mic"
- android:layout_width="?attr/notificationHeaderIconSize"
- android:layout_height="?attr/notificationHeaderIconSize"
- android:src="@drawable/ic_mic"
- android:layout_marginStart="4dp"
- android:visibility="gone"
- android:focusable="false"
- android:contentDescription="@string/notification_appops_microphone_active"
- />
- <ImageView
- android:id="@+id/overlay"
- android:layout_width="?attr/notificationHeaderIconSize"
- android:layout_height="?attr/notificationHeaderIconSize"
- android:src="@drawable/ic_alert_window_layer"
- android:layout_marginStart="4dp"
- android:visibility="gone"
- android:focusable="false"
- android:contentDescription="@string/notification_appops_overlay_active"
- />
- </LinearLayout>
- </LinearLayout>
-
<!-- Messages -->
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 7cda03f..7656dd5 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -26,7 +26,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:singleLine="true"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:visibility="?attr/notificationHeaderAppNameVisibility"
/>
@@ -34,7 +34,7 @@
android:id="@+id/header_text_secondary_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
@@ -45,7 +45,7 @@
android:id="@+id/header_text_secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:visibility="gone"
@@ -56,7 +56,7 @@
android:id="@+id/header_text_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
@@ -67,7 +67,7 @@
android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:visibility="gone"
@@ -78,7 +78,7 @@
android:id="@+id/time_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
diff --git a/core/res/res/values-television/styles_device_defaults.xml b/core/res/res/values-television/styles_device_defaults.xml
new file mode 100644
index 0000000..ef55fc6
--- /dev/null
+++ b/core/res/res/values-television/styles_device_defaults.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.
+ -->
+<resources>
+ <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog"
+ parent="Widget.Leanback.Button.ButtonBar" />
+</resources>
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e567c3d..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 -->
<!-- =================== -->
@@ -3251,6 +3260,16 @@
a value of 'true' will not override any 'false' value in its parent chain nor will
it prevent any 'false' in any of its children. -->
<attr name="forceDarkAllowed" format="boolean" />
+
+ <!-- <p>Whether the View's Outline should be used to clip the contents of the View.
+ <p>Only a single non-rectangular clip can be applied on a View at any time. Circular
+ clips from a
+ {@link android.view.ViewAnimationUtils#createCircularReveal(View, int, int, float,
+ float)} circular reveal animation take priority over Outline clipping, and child
+ Outline clipping takes priority over Outline clipping done by a parent.
+ <p>Note that this flag will only be respected if the View's Outline returns true from
+ {@link android.graphics.Outline#canClip()}. -->
+ <attr name="clipToOutline" format="boolean" />
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -9040,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/colors.xml b/core/res/res/values/colors.xml
index 20a5d37..5546621 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -153,6 +153,11 @@
<color name="notification_action_list_background_color">@null</color>
+ <!-- The color of the Decline and Hang Up actions on a CallStyle notification -->
+ <color name="call_notification_decline_color">#d93025</color>
+ <!-- The color of the Answer action on a CallStyle notification -->
+ <color name="call_notification_answer_color">#1e8e3e</color>
+
<!-- Keyguard colors -->
<color name="keyguard_avatar_frame_color">#ffffffff</color>
<color name="keyguard_avatar_frame_shadow_color">#80000000</color>
diff --git a/core/res/res/values/colors_leanback.xml b/core/res/res/values/colors_leanback.xml
index e52a861e..df0be03 100644
--- a/core/res/res/values/colors_leanback.xml
+++ b/core/res/res/values/colors_leanback.xml
@@ -26,4 +26,9 @@
<color name="secondary_text_leanback_light">#99222222</color>
<color name="primary_text_leanback_formwizard_default_dark">#ffeeeeee</color>
+
+ <color name="btn_leanback_focused">#E8EAED</color>
+ <color name="btn_leanback_unfocused">#1AFFFFFF</color>
+ <color name="btn_text_leanback_focused">#0E0E0E</color>
+ <color name="btn_text_leanback_unfocused">#E8EAED</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8c5f454..09ca12a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -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>
@@ -4712,4 +4710,10 @@
<!-- Whether to select voice/data/sms preference without user confirmation -->
<bool name="config_voice_data_sms_auto_fallback">false</bool>
+
+ <!-- Whether to enable the one-handed keyguard on the lock screen for wide-screen devices. -->
+ <bool name="config_enableOneHandedKeyguard">false</bool>
+
+ <!-- Whether to allow the caching of the SIM PIN for verification after unattended reboot -->
+ <bool name="config_allow_pin_storage_for_unattended_reboot">true</bool>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index cb16af5..debdab0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -236,9 +236,20 @@
value is calculated in ConversationLayout#updateActionListPadding() -->
<dimen name="notification_actions_padding_start">36dp</dimen>
+ <!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
+ notification actions.
+ this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
+ <dimen name="call_notification_collapsible_indent">64dp</dimen>
+
<!-- The size of icons for visual actions in the notification_material_action_list -->
<dimen name="notification_actions_icon_size">48dp</dimen>
+ <!-- The size of icons for visual actions in the notification_material_action_list -->
+ <dimen name="notification_actions_icon_drawable_size">20dp</dimen>
+
+ <!-- The corner radius if the emphasized action buttons in a notification -->
+ <dimen name="notification_action_button_radius">8dp</dimen>
+
<!-- Size of the stroke with for the emphasized notification button style -->
<dimen name="emphasized_button_stroke_width">1dp</dimen>
@@ -383,10 +394,6 @@
<dimen name="alert_dialog_button_bar_width">64dp</dimen>
<!-- Dialog button bar height -->
<dimen name="alert_dialog_button_bar_height">48dip</dimen>
- <!-- Leanback dialog vertical margin -->
- <dimen name="leanback_alert_dialog_vertical_margin">27dip</dimen>
- <!-- Leanback dialog horizontal margin -->
- <dimen name="leanback_alert_dialog_horizontal_margin">54dip</dimen>
<!-- Default height of an action bar. -->
<dimen name="action_bar_default_height">48dip</dimen>
diff --git a/core/res/res/values/dimens_leanback.xml b/core/res/res/values/dimens_leanback.xml
index c824a2a..3ab2196 100644
--- a/core/res/res/values/dimens_leanback.xml
+++ b/core/res/res/values/dimens_leanback.xml
@@ -83,4 +83,10 @@
<dimen name="leanback_setup_translation_backward_out_content_end_v4">@integer/leanback_setup_translation_content_cliff_v4</dimen>
<integer name="leanback_setup_translation_backward_out_content_delay">0</integer>
<integer name="leanback_setup_translation_backward_out_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <!-- Button dimens -->
+ <dimen name="leanback_button_height">42dp</dimen>
+ <dimen name="leanback_button_radius">55dp</dimen>
+ <dimen name="leanback_button_padding_horizontal">22dp</dimen>
+ <dimen name="leanback_button_padding_vertical">11dp</dimen>
</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index a4c7293..ab4e0f3 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -102,6 +102,10 @@
<item type="id" name="selection_end_handle" />
<item type="id" name="insertion_handle" />
<item type="id" name="floating_toolbar_menu_item_image_button" />
+ <item type="id" name="camera" />
+ <item type="id" name="mic" />
+ <item type="id" name="overlay" />
+ <item type="id" name="app_ops" />
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_ON_SCREEN}. -->
<item type="id" name="accessibilityActionShowOnScreen" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 97ec0f4..068987e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3062,6 +3062,9 @@
<!-- @hide @SystemApi -->
<public name="hotwordDetectionService" />
<public name="previewLayout" />
+ <public name="clipToOutline" />
+ <public name="edgeEffectType" />
+ <public name="knownCerts" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
@@ -3116,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/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9b5f670..af5e406 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5039,6 +5039,18 @@
<!-- Tempalate for Notification.MessagingStyle to join a conversation name with the name of the sender of a message, to make a notification title [CHAR LIMIT=NONE] -->
<string name="notification_messaging_title_template"><xliff:g id="conversation_title" example="Tasty Treat Team">%1$s</xliff:g>: <xliff:g id="sender_name" example="Adrian Baker">%2$s</xliff:g></string>
+ <!-- Action text to be displayed for the "answer" action of an incoming call [CHAR LIMIT=13] -->
+ <string name="call_notification_answer_action">Answer</string>
+ <!-- Action text to be displayed for the "decline" action of an incoming call [CHAR LIMIT=13] -->
+ <string name="call_notification_decline_action">Decline</string>
+ <!-- Action text to be displayed for the "hang up" action of an ongoing call [CHAR LIMIT=13] -->
+ <string name="call_notification_hang_up_action">Hang Up</string>
+ <!-- Default notification text to be displayed in incoming call notifications [CHAR LIMIT=40] -->
+ <string name="call_notification_incoming_text">Incoming call</string>
+ <!-- Default notification text to be displayed in ongoing call notifications [CHAR LIMIT=40] -->
+ <string name="call_notification_ongoing_text">Ongoing call</string>
+ <!-- Default notification text to be displayed in screening call notifications [CHAR LIMIT=40] -->
+ <string name="call_notification_screening_text">Screening an incoming call</string>
<!-- Label describing the number of selected items [CHAR LIMIT=48] -->
<plurals name="selected_count">
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index aaeaadd..7eaf36d 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -16,11 +16,34 @@
<resources>
<style name="AlertDialog.Leanback" parent="AlertDialog.Material">
<item name="buttonPanelSideLayout">@android:layout/alert_dialog_leanback_button_panel_side</item>
+ <item name="layout">@android:layout/alert_dialog_leanback</item>
</style>
<style name="AlertDialog.Leanback.Light">
</style>
+ <style name="Widget.Leanback.Button" parent="Widget.Material.Button">
+ <item name="android:background">@drawable/btn_leanback</item>
+ <item name="android:textColor">@color/btn_leanback_text_color</item>
+ <item name="android:layout_height">@dimen/leanback_button_height</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:paddingHorizontal">@dimen/leanback_button_padding_horizontal</item>
+ <item name="android:paddingVertical">@dimen/leanback_button_padding_vertical</item>
+ </style>
+
+ <style name="Widget.Leanback.Button.ButtonBar" parent="Widget.Leanback.Button">
+ <item name="android:layout_marginStart">10dp</item>
+ </style>
+
+ <style name="Widget.Leanback.Button.ButtonBarGravityStart" parent="Widget.Leanback.Button">
+ <item name="android:layout_marginEnd">10dp</item>
+ </style>
+
+ <style name="Widget.Leanback.ButtonBar" parent="Widget.Material.ButtonBar">
+ <item name="android:padding">?android:attr/dialogPreferredPadding</item>
+ </style>
+
<style name="Widget.Leanback.TimePicker" parent="Widget.Material.TimePicker">
<item name="timePickerMode">spinner</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c41b78e..4109d4c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2647,6 +2647,7 @@
<java-symbol type="bool" name="config_defaultWindowFeatureContextMenu" />
<java-symbol type="bool" name="config_overrideRemoteViewsActivityTransition" />
<java-symbol type="attr" name="colorProgressBackgroundNormal" />
+ <java-symbol type="bool" name="config_allow_pin_storage_for_unattended_reboot" />
<java-symbol type="layout" name="simple_account_item" />
<java-symbol type="string" name="prohibit_manual_network_selection_in_gobal_mode" />
@@ -3088,8 +3089,26 @@
<!-- TV Remote Service package -->
<java-symbol type="string" name="config_tvRemoteServicePackage" />
+
+ <!-- Notifications: MessagingStyle -->
<java-symbol type="string" name="notification_messaging_title_template" />
+ <!-- Notifications: CallStyle -->
+ <java-symbol type="layout" name="notification_template_material_call" />
+ <java-symbol type="string" name="call_notification_answer_action" />
+ <java-symbol type="string" name="call_notification_decline_action" />
+ <java-symbol type="string" name="call_notification_hang_up_action" />
+ <java-symbol type="string" name="call_notification_incoming_text" />
+ <java-symbol type="string" name="call_notification_ongoing_text" />
+ <java-symbol type="string" name="call_notification_screening_text" />
+ <java-symbol type="color" name="call_notification_decline_color"/>
+ <java-symbol type="color" name="call_notification_answer_color"/>
+ <java-symbol type="dimen" name="call_notification_collapsible_indent"/>
+ <java-symbol type="drawable" name="ic_call_answer" />
+ <java-symbol type="drawable" name="ic_call_decline" />
+ <java-symbol type="id" name="verification_icon" />
+ <java-symbol type="id" name="verification_text" />
+
<!-- Notification handler / dashboard package -->
<java-symbol type="string" name="config_notificationHandlerPackage" />
@@ -3415,6 +3434,7 @@
<java-symbol type="dimen" name="notification_media_image_max_width"/>
<java-symbol type="dimen" name="notification_media_image_max_height"/>
<java-symbol type="dimen" name="notification_right_icon_size"/>
+ <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index 9dca912..efe5826 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -22,6 +22,8 @@
<item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
<item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
<item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+ <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
</style>
<style name="Theme.Leanback.Light.Dialog" parent="Theme.Material.Light.BaseDialog">
@@ -32,6 +34,8 @@
<item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
<item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
<item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+ <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
</style>
<style name="Theme.Leanback.Settings.Dialog" parent="Theme.Material.Settings.BaseDialog">
@@ -42,6 +46,8 @@
<item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
<item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
<item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+ <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
</style>
<style name="Theme.Leanback.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert">
@@ -52,6 +58,8 @@
<item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
<item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
<item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+ <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
</style>
<style name="Theme.Leanback.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert">
@@ -62,6 +70,8 @@
<item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
<item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
<item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+ <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
</style>
<style name="Theme.Leanback.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.BaseAlert">
@@ -72,6 +82,8 @@
<item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
<item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
<item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+ <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
</style>
<style name="Theme.Leanback.Dialog.AppError" parent="Theme.Leanback.Dialog">
diff --git a/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java b/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
index cfcfcc8..ece37f8 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
@@ -201,6 +201,18 @@
assertThat(BundleUtil.deepHashCode(b1)).isNotEqualTo(BundleUtil.deepHashCode(b2));
}
+ @Test
+ public void testDeepHashCode_differentKeys() {
+ Bundle[] inputs = new Bundle[2];
+ for (int i = 0; i < 2; i++) {
+ Bundle b = new Bundle();
+ b.putString("key" + i, "value");
+ inputs[i] = b;
+ }
+ assertThat(BundleUtil.deepHashCode(inputs[0]))
+ .isNotEqualTo(BundleUtil.deepHashCode(inputs[1]));
+ }
+
private static Bundle createThoroughBundle() {
Bundle toy1 = new Bundle();
toy1.putString("a", "a");
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/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/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index 64906bb..e16d448 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -100,7 +100,7 @@
final List<UsageStats> slices = new ArrayList<>();
slices.add(packageStats);
ParceledListSlice<UsageStats> stats = new ParceledListSlice<>(slices);
- when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString()))
+ when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString(), anyInt()))
.thenReturn(stats);
Answer<Void> answer = new Answer<Void>() {
@Override
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..4c52848 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,29 @@
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) {
+
+ assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
+ bi.getCustomMeasuredEnergyMicroJoules(0));
+
+ assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
+ bi.getCustomMeasuredEnergyMicroJoules(1));
+
+ assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A,
+ bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(0));
+
+ assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B,
+ bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(1));
+
+ assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A,
+ bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(0));
+
+ assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B,
+ bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(1));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 143e07a..0fac4f7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -45,6 +45,7 @@
BluetoothPowerCalculatorTest.class,
BstatsCpuTimesValidationTest.class,
CameraPowerCalculatorTest.class,
+ CpuPowerCalculatorTest.class,
FlashlightPowerCalculatorTest.class,
GnssPowerCalculatorTest.class,
IdlePowerCalculatorTest.class,
@@ -65,6 +66,7 @@
ScreenPowerCalculatorTest.class,
SensorPowerCalculatorTest.class,
SystemServicePowerCalculatorTest.class,
+ UserPowerCalculatorTest.class,
VideoPowerCalculatorTest.class,
com.android.internal.power.MeasuredEnergyStatsTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 2c71287..0ddc4c0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -28,6 +28,7 @@
import android.os.BatteryUsageStatsQuery;
import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
+import android.os.UserBatteryConsumer;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -76,6 +77,26 @@
return this;
}
+ public BatteryUsageStatsRule setNumCpuClusters(int number) {
+ when(mPowerProfile.getNumCpuClusters()).thenReturn(number);
+ return this;
+ }
+
+ public BatteryUsageStatsRule setNumSpeedStepsInCpuCluster(int cluster, int speeds) {
+ when(mPowerProfile.getNumSpeedStepsInCpuCluster(cluster)).thenReturn(speeds);
+ return this;
+ }
+
+ public BatteryUsageStatsRule setAveragePowerForCpuCluster(int cluster, double value) {
+ when(mPowerProfile.getAveragePowerForCpuCluster(cluster)).thenReturn(value);
+ return this;
+ }
+
+ public BatteryUsageStatsRule setAveragePowerForCpuCore(int cluster, int step, double value) {
+ when(mPowerProfile.getAveragePowerForCpuCore(cluster, step)).thenReturn(value);
+ return this;
+ }
+
public void setNetworkStats(NetworkStats networkStats) {
mBatteryStats.setNetworkStats(networkStats);
}
@@ -113,15 +134,21 @@
mMockClocks.uptime = uptimeUs;
}
- void apply(PowerCalculator calculator) {
+ void apply(PowerCalculator... calculators) {
+ apply(BatteryUsageStatsQuery.DEFAULT, calculators);
+ }
+
+ void apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0);
SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
for (int i = 0; i < uidStats.size(); i++) {
builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
}
- calculator.calculate(builder, mBatteryStats, mMockClocks.realtime, mMockClocks.uptime,
- BatteryUsageStatsQuery.DEFAULT, null);
+ for (PowerCalculator calculator : calculators) {
+ calculator.calculate(builder, mBatteryStats, mMockClocks.realtime, mMockClocks.uptime,
+ query);
+ }
mBatteryUsageStats = builder.build();
}
@@ -144,4 +171,13 @@
}
return null;
}
+
+ public UserBatteryConsumer getUserBatteryConsumer(int userId) {
+ for (UserBatteryConsumer ubc : mBatteryUsageStats.getUserBatteryConsumers()) {
+ if (ubc.getUserId() == userId) {
+ return ubc;
+ }
+ }
+ return null;
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
new file mode 100644
index 0000000..9cf0d37
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CpuPowerCalculatorTest {
+ 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 + 272;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+ .setNumCpuClusters(2)
+ .setNumSpeedStepsInCpuCluster(0, 2)
+ .setNumSpeedStepsInCpuCluster(1, 2)
+ .setAveragePowerForCpuCluster(0, 360)
+ .setAveragePowerForCpuCluster(1, 480)
+ .setAveragePowerForCpuCore(0, 0, 300)
+ .setAveragePowerForCpuCore(0, 1, 400)
+ .setAveragePowerForCpuCore(1, 0, 500)
+ .setAveragePowerForCpuCore(1, 1, 600);
+
+ private final KernelCpuSpeedReader[] mMockKernelCpuSpeedReaders = new KernelCpuSpeedReader[]{
+ mock(KernelCpuSpeedReader.class),
+ mock(KernelCpuSpeedReader.class),
+ };
+
+ @Mock
+ private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
+ @Mock
+ private KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader mMockKernelCpuUidClusterTimeReader;
+ @Mock
+ private KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
+ @Mock
+ private KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader mMockKernelCpuUidUserSysTimeReader;
+ @Mock
+ private KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader mMockKerneCpuUidActiveTimeReader;
+ @Mock
+ private SystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mStatsRule.getBatteryStats()
+ .setUserInfoProvider(mMockUserInfoProvider)
+ .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
+ .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
+ .setKernelCpuUidClusterTimeReader(mMockKernelCpuUidClusterTimeReader)
+ .setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader)
+ .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
+ .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader);
+ }
+
+ @Test
+ public void testTimerBasedModel() {
+ when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
+
+ when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000});
+ when(mMockKernelCpuSpeedReaders[1].readDelta()).thenReturn(new long[]{3000, 4000});
+
+ when(mMockCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(false);
+
+ // User/System CPU time
+ doAnswer(invocation -> {
+ final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+ // User/system time in microseconds
+ callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000});
+ callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000});
+ return null;
+ }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(any());
+
+ // Active CPU time
+ doAnswer(invocation -> {
+ final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(0);
+ callback.onUidCpuTime(APP_UID1, 1111L);
+ callback.onUidCpuTime(APP_UID2, 3333L);
+ return null;
+ }).when(mMockKerneCpuUidActiveTimeReader).readDelta(any());
+
+ // Per-cluster CPU time
+ doAnswer(invocation -> {
+ final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+ callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222});
+ callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444});
+ return null;
+ }).when(mMockKernelCpuUidClusterTimeReader).readDelta(any());
+
+ mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true);
+
+ mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234);
+ mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345);
+
+ CpuPowerCalculator calculator =
+ new CpuPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+ .isEqualTo(3333);
+ assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(1.092233);
+ assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
+
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+ .isEqualTo(7777);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(2.672322);
+ assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+ }
+}
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/os/UserPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
new file mode 100644
index 0000000..6fa1d3b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+import android.os.UserBatteryConsumer;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UserPowerCalculatorTest {
+ public static final int USER1 = 0;
+ public static final int USER2 = 1625;
+
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 272;
+ private static final int APP_UID3 = Process.FIRST_APPLICATION_UID + 314;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+
+ @Test
+ public void testAllUsers() {
+ prepareUidBatteryConsumers();
+
+ UserPowerCalculator calculator = new UserPowerCalculator();
+
+ mStatsRule.apply(BatteryUsageStatsQuery.DEFAULT, calculator, new FakeAudioPowerCalculator(),
+ new FakeVideoPowerCalculator());
+
+ assertThat(mStatsRule.getUserBatteryConsumer(USER1)).isNull();
+
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(3000);
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(7000);
+
+ assertThat(mStatsRule.getUserBatteryConsumer(USER2)).isNull();
+
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(5555);
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(9999);
+ }
+
+ @Test
+ public void testSpecificUser() {
+ prepareUidBatteryConsumers();
+
+ UserPowerCalculator calculator = new UserPowerCalculator();
+
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().addUser(UserHandle.of(USER1)).build(),
+ calculator, new FakeAudioPowerCalculator(), new FakeVideoPowerCalculator());
+
+ assertThat(mStatsRule.getUserBatteryConsumer(USER1)).isNull();
+
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(3000);
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(7000);
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID2))).isNull();
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID3))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(7070);
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID3))
+ .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(11110);
+
+ UserBatteryConsumer user2 = mStatsRule.getUserBatteryConsumer(USER2);
+ assertThat(user2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO))
+ .isEqualTo(15308);
+ assertThat(user2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO))
+ .isEqualTo(24196);
+
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID1))).isNull();
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))).isNull();
+ assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID3))).isNull();
+ }
+
+ private void prepareUidBatteryConsumers() {
+ prepareUidBatteryConsumer(USER1, APP_UID1, 1000, 2000, 3000, 4000);
+ prepareUidBatteryConsumer(USER2, APP_UID2, 2222, 3333, 4444, 5555);
+ prepareUidBatteryConsumer(USER1, APP_UID3, 3030, 4040, 5050, 6060);
+ prepareUidBatteryConsumer(USER2, APP_UID3, 4321, 5432, 6543, 7654);
+ }
+
+ private void prepareUidBatteryConsumer(int userId, int uid, long audioDuration1Ms,
+ long audioDuration2Ms, long videoDuration1Ms, long videoDuration2Ms) {
+ BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(UserHandle.getUid(userId, uid));
+
+ // Use "audio" and "video" to fake some power consumption. Could be any other type of usage.
+ uidStats.noteAudioTurnedOnLocked(0);
+ uidStats.noteAudioTurnedOffLocked(audioDuration1Ms);
+ uidStats.noteAudioTurnedOnLocked(1000000);
+ uidStats.noteAudioTurnedOffLocked(1000000 + audioDuration2Ms);
+
+ uidStats.noteVideoTurnedOnLocked(0);
+ uidStats.noteVideoTurnedOffLocked(videoDuration1Ms);
+ uidStats.noteVideoTurnedOnLocked(2000000);
+ uidStats.noteVideoTurnedOffLocked(2000000 + videoDuration2Ms);
+ }
+
+ private static class FakeAudioPowerCalculator extends PowerCalculator {
+ @Override
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ long durationMs = u.getAudioTurnedOnTimer().getTotalTimeLocked(rawRealtimeUs, 0);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO, durationMs / 1000);
+ }
+ }
+
+ private static class FakeVideoPowerCalculator extends PowerCalculator {
+ @Override
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ long durationMs = u.getVideoTurnedOnTimer().getTotalTimeLocked(rawRealtimeUs, 0);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO, durationMs / 1000);
+ }
+ }
+}
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..1679774 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,24 @@
}
@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 testReset() {
final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
final int numCustomBuckets = 2;
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/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.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.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/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index cb4dd9e..b70fa0e 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -46,7 +46,6 @@
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.stream.Stream;
@@ -1148,24 +1147,14 @@
// Default to SRGB if the display doesn't support wide color
.orElse(Dataspace.SRGB);
- float maxRefreshRate =
- (float) Arrays.stream(display.getSupportedModes())
- .mapToDouble(Mode::getRefreshRate)
- .max()
- .orElseGet(() -> {
- Log.i(LOG_TAG, "Failed to find the maximum display refresh rate");
- // Assume that the max refresh rate is 60hz if we can't find one.
- return 60.0;
- });
// Grab the physical screen dimensions from the active display mode
// Strictly speaking the screen resolution may not always be constant - it is for
// sizing the font cache for the underlying rendering thread. Since it's a
// heuristic we don't need to be always 100% correct.
Mode activeMode = display.getMode();
nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
- display.getRefreshRate(), maxRefreshRate,
- wideColorDataspace.mNativeDataspace, display.getAppVsyncOffsetNanos(),
- display.getPresentationDeadlineNanos());
+ display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
+ display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
// Defensively clear out the context
mContext = null;
@@ -1324,6 +1313,5 @@
private static native void nSetDisplayDensityDpi(int densityDpi);
private static native void nInitDisplayInfo(int width, int height, float refreshRate,
- float maxRefreshRate, int wideColorDataspace, long appVsyncOffsetNanos,
- long presentationDeadlineNanos);
+ int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos);
}
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/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/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java
index 372add9..d188b65 100644
--- a/keystore/java/android/security/KeyStoreSecurityLevel.java
+++ b/keystore/java/android/security/KeyStoreSecurityLevel.java
@@ -190,7 +190,7 @@
keyDescriptor.blob = wrappedKey;
keyDescriptor.domain = wrappedKeyDescriptor.domain;
- return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor,
+ return handleExceptions(() -> mSecurityLevel.importWrappedKey(keyDescriptor,
wrappingKeyDescriptor, maskingKey,
args.toArray(new KeyParameter[args.size()]), authenticatorSpecs));
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 16bf546..0871517 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -434,14 +434,16 @@
@NonNull
public static java.security.KeyStore getKeyStoreForUid(int uid)
throws KeyStoreException, NoSuchProviderException {
- String providerName = PROVIDER_NAME;
+ final java.security.KeyStore.LoadStoreParameter loadParameter;
if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) {
- providerName = "AndroidKeyStoreLegacy";
+ loadParameter = new android.security.keystore2.AndroidKeyStoreLoadStoreParameter(
+ KeyProperties.legacyUidToNamespace(uid));
+ } else {
+ loadParameter = new AndroidKeyStoreLoadStoreParameter(uid);
}
- java.security.KeyStore result =
- java.security.KeyStore.getInstance(providerName);
+ java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME);
try {
- result.load(new AndroidKeyStoreLoadStoreParameter(uid));
+ result.load(loadParameter);
} catch (NoSuchAlgorithmException | CertificateException | IOException e) {
throw new KeyStoreException(
"Failed to load AndroidKeyStore KeyStore for UID " + uid, e);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
index 8475ad9..0f77749 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
@@ -164,6 +164,9 @@
List<KeyParameter> parameters = new ArrayList<>();
parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_SIGN
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
index 32650ae..5619585 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
@@ -21,7 +21,6 @@
import android.system.keystore2.Authorization;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
-import android.util.Log;
import java.security.Key;
@@ -127,15 +126,6 @@
return false;
}
- // If the key ids are equal and the class matches all the other fields cannot differ
- // unless we have a bug.
- if (!mAlgorithm.equals(other.mAlgorithm)
- || !mAuthorizations.equals(other.mAuthorizations)
- || !mDescriptor.equals(other.mDescriptor)) {
- Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata"
- + "differs.");
- return false;
- }
return true;
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 8c8acc4..39607ae 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -866,7 +866,8 @@
try {
response = mKeyStore.getKeyEntry(wrappingkey);
} catch (android.security.KeyStoreException e) {
- throw new KeyStoreException("Failed to load wrapping key.", e);
+ throw new KeyStoreException("Failed to import wrapped key. Keystore error code: "
+ + e.getErrorCode(), e);
}
KeyDescriptor wrappedKey = makeKeyDescriptor(alias);
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/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 40fdb97..0ee1f06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -398,6 +398,14 @@
}
}
+ @Override
+ public void setExpandedContentAlpha(float alpha) {
+ if (mExpandedView != null) {
+ mExpandedView.setAlpha(alpha);
+ mExpandedView.setTaskViewAlpha(alpha);
+ }
+ }
+
/**
* Set visibility of bubble in the expanded state.
*
@@ -407,7 +415,7 @@
* and setting {@code false} actually means rendering the expanded view in transparent.
*/
@Override
- public void setContentVisibility(boolean visibility) {
+ public void setTaskViewVisibility(boolean visibility) {
if (mExpandedView != null) {
mExpandedView.setContentVisibility(visibility);
}
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..2391a08 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
@@ -192,9 +192,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 +211,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 +322,7 @@
}
@VisibleForTesting
- public Bubbles getImpl() {
+ public Bubbles asBubbles() {
return mImpl;
}
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 29458ef..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
@@ -85,6 +85,19 @@
private boolean mImeVisible;
private boolean mNeedsNewHeight;
+ /**
+ * Whether we want the TaskView's content to be visible (alpha = 1f). If
+ * {@link #mIsAlphaAnimating} is true, this may not reflect the TaskView's actual alpha value
+ * until the animation ends.
+ */
+ private boolean mIsContentVisible = false;
+
+ /**
+ * Whether we're animating the TaskView's alpha value. If so, we will hold off on applying alpha
+ * changes from {@link #setContentVisibility} until the animation ends.
+ */
+ private boolean mIsAlphaAnimating = false;
+
private int mMinHeight;
private int mOverflowHeight;
private int mSettingsIconHeight;
@@ -150,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();
}
@@ -465,6 +479,29 @@
}
/**
+ * Whether we are currently animating the TaskView's alpha value. If this is set to true, calls
+ * to {@link #setContentVisibility} will not be applied until this is set to false again.
+ */
+ void setAlphaAnimating(boolean animating) {
+ mIsAlphaAnimating = animating;
+
+ // If we're done animating, apply the correct
+ if (!animating) {
+ setContentVisibility(mIsContentVisible);
+ }
+ }
+
+ /**
+ * Sets the alpha of the underlying TaskView, since changing the expanded view's alpha does not
+ * affect the TaskView since it uses a Surface.
+ */
+ void setTaskViewAlpha(float alpha) {
+ if (mTaskView != null) {
+ mTaskView.setAlpha(alpha);
+ }
+ }
+
+ /**
* Set visibility of contents in the expanded state.
*
* @param visibility {@code true} if the contents should be visible on the screen.
@@ -477,16 +514,19 @@
Log.d(TAG, "setContentVisibility: visibility=" + visibility
+ " bubble=" + getBubbleKey());
}
+ mIsContentVisible = visibility;
+
final float alpha = visibility ? 1f : 0f;
mPointerView.setAlpha(alpha);
- if (mTaskView != null) {
+ if (mTaskView != null && !mIsAlphaAnimating) {
mTaskView.setAlpha(alpha);
}
}
+
@Nullable
- View getTaskView() {
+ TaskView getTaskView() {
return mTaskView;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 16cd3cf..51d63cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -167,8 +167,12 @@
return dotPath
}
- override fun setContentVisibility(visible: Boolean) {
- expandedView?.setContentVisibility(visible)
+ override fun setExpandedContentAlpha(alpha: Float) {
+ expandedView?.alpha = alpha
+ }
+
+ override fun setTaskViewVisibility(visible: Boolean) {
+ // Overflow does not have a TaskView.
}
override fun getIconView(): BadgedImageView? {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d54be0e..a3edc20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -43,7 +43,6 @@
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
-import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import android.view.Choreographer;
@@ -123,6 +122,10 @@
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
+ private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
+
+ private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+
/**
* How long to wait to animate the stack temporarily invisible after a drag/flyout hide
* animation ends, if we are in fact temporarily invisible.
@@ -142,7 +145,7 @@
private final PhysicsAnimator.SpringConfig mTranslateSpringConfig =
new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+ SpringForce.STIFFNESS_VERY_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
/**
* Handler to use for all delayed animations - this way, we can easily cancel them before
@@ -211,6 +214,9 @@
/** Container for the animating-out SurfaceView. */
private FrameLayout mAnimatingOutSurfaceContainer;
+ /** Animator for animating the alpha value of the animating out SurfaceView. */
+ private final ValueAnimator mAnimatingOutSurfaceAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+
/**
* Buffer containing a screenshot of the animating-out bubble. This is drawn into the
* SurfaceView during animations.
@@ -261,6 +267,12 @@
/** Whether we're in the middle of dragging the stack around by touch. */
private boolean mIsDraggingStack = false;
+ /** Whether the expanded view has been hidden, because we are dragging out a bubble. */
+ private boolean mExpandedViewHidden = false;
+
+ /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
+ private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+
/**
* The pointer index of the ACTION_DOWN event we received prior to an ACTION_UP. We'll ignore
* touches from other pointer indices.
@@ -614,6 +626,12 @@
// Show the dismiss target, if we haven't already.
mDismissView.show();
+ if (mIsExpanded && mExpandedBubble != null && v.equals(mExpandedBubble.getIconView())) {
+ // Hide the expanded view if we're dragging out the expanded bubble, and we haven't
+ // already hidden it.
+ hideExpandedViewIfNeeded();
+ }
+
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
// bubble since it's stuck to the target.
if (!passEventToMagnetizedObject(ev)) {
@@ -645,6 +663,9 @@
if (!passEventToMagnetizedObject(ev)) {
if (mBubbleData.isExpanded()) {
mExpandedAnimationController.snapBubbleBack(v, velX, velY);
+
+ // Re-show the expanded view if we hid it.
+ showExpandedViewIfNeeded();
} else {
// Fling the stack to the edge, and save whether or not it's going to end up on
// the left side of the screen.
@@ -937,6 +958,46 @@
animate()
.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
.setDuration(FADE_IN_DURATION);
+
+ mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
+ mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // We need to be Z ordered on top in order for alpha animations to work.
+ mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
+ mExpandedBubble.getExpandedView().setAlphaAnimating(true);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+ mExpandedBubble.getExpandedView().setAlphaAnimating(false);
+ }
+ }
+ });
+ mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
+ if (mExpandedBubble != null) {
+ mExpandedBubble.setExpandedContentAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ });
+
+ mAnimatingOutSurfaceAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
+ mAnimatingOutSurfaceAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ mAnimatingOutSurfaceAlphaAnimator.addUpdateListener(valueAnimator -> {
+ if (!mExpandedViewHidden) {
+ mAnimatingOutSurfaceView.setAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ });
+ mAnimatingOutSurfaceAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ releaseAnimatingOutBubbleBuffer();
+ }
+ });
}
/**
@@ -1539,7 +1600,8 @@
// If we're expanded, screenshot the currently expanded bubble (before expanding the newly
// selected bubble) so we can animate it out.
- if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null
+ && !mExpandedViewHidden) {
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
// Before screenshotting, have the real ActivityView show on top of other surfaces
// so that the screenshot doesn't flicker on top of it.
@@ -1575,7 +1637,7 @@
mExpandedViewContainer.setAlpha(0.0f);
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
if (previouslySelected != null) {
- previouslySelected.setContentVisibility(false);
+ previouslySelected.setTaskViewVisibility(false);
}
updateExpandedBubble();
@@ -1656,6 +1718,58 @@
requestUpdate();
}
+ /** Animate the expanded view hidden. This is done while we're dragging out a bubble. */
+ private void hideExpandedViewIfNeeded() {
+ if (mExpandedViewHidden
+ || mExpandedBubble == null
+ || mExpandedBubble.getExpandedView() == null) {
+ return;
+ }
+
+ mExpandedViewHidden = true;
+
+ // Scale down.
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+ .spring(AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+ mScaleOutSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_Y,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+ mScaleOutSpringConfig)
+ .addUpdateListener((target, values) ->
+ mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix))
+ .start();
+
+ // Animate alpha from 1f to 0f.
+ mExpandedViewAlphaAnimator.reverse();
+ }
+
+ /**
+ * Animate the expanded view visible again. This is done when we're done dragging out a bubble.
+ */
+ private void showExpandedViewIfNeeded() {
+ if (!mExpandedViewHidden) {
+ return;
+ }
+
+ mExpandedViewHidden = false;
+
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+ .spring(AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleOutSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_Y,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleOutSpringConfig)
+ .addUpdateListener((target, values) ->
+ mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix))
+ .start();
+
+ mExpandedViewAlphaAnimator.start();
+ }
+
private void animateExpansion() {
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
@@ -1714,7 +1828,7 @@
// Should not happen since we lay out before expanding, but just in case...
if (getWidth() > 0) {
startDelay = (long)
- (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION
+ (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 1.2f
+ (distanceAnimated / getWidth()) * 30);
}
@@ -1728,20 +1842,29 @@
pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
}
mExpandedViewContainerMatrix.setScale(
- 0f, 0f,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
pivotX, pivotY);
} else {
mExpandedViewContainerMatrix.setScale(
- 0f, 0f,
- bubbleWillBeAt + mBubbleSize / 2f, getExpandedViewY());
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+ bubbleWillBeAt + mBubbleSize / 2f,
+ getExpandedViewY());
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+ if (mExpandedBubble.getExpandedView() != null) {
+ mExpandedBubble.setExpandedContentAlpha(0f);
+
+ // We'll be starting the alpha animation after a slight delay, so set this flag early
+ // here.
+ mExpandedBubble.getExpandedView().setAlphaAnimating(true);
}
mDelayedAnimationExecutor.executeDelayed(() -> {
+ mExpandedViewAlphaAnimator.start();
+
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
.spring(AnimatableScaleMatrix.SCALE_X,
@@ -1796,22 +1919,15 @@
// since we're about to animate collapsed.
mExpandedAnimationController.notifyPreparingToCollapse();
- final long startDelay =
- (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f);
- mDelayedAnimationExecutor.executeDelayed(() -> {
- mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
- /* collapseTo */,
- () -> mBubbleContainer.setActiveController(mStackAnimationController));
- }, startDelay);
+ mExpandedAnimationController.collapseBackToStack(
+ mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+ /* collapseTo */,
+ () -> mBubbleContainer.setActiveController(mStackAnimationController));
if (mTaskbarScrim.getVisibility() == VISIBLE) {
mTaskbarScrim.animate().alpha(0f).start();
}
- // We want to visually collapse into this bubble during the animation.
- final View expandingFromBubble = mExpandedBubble.getIconView();
-
int index;
if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
index = mBubbleData.getBubbles().size();
@@ -1840,31 +1956,25 @@
getExpandedViewY());
}
+ mExpandedViewAlphaAnimator.reverse();
+
+ // When the animation completes, we should no longer be showing the content.
+ if (mExpandedBubble.getExpandedView() != null) {
+ mExpandedBubble.getExpandedView().setContentVisibility(false);
+ }
+
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
- .spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig)
- .spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+ mScaleOutSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_Y,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+ mScaleOutSpringConfig)
.addUpdateListener((target, values) -> {
- if (expandingFromBubble != null) {
- // Follow the bubble as it translates!
- if (showVertically) {
- mExpandedViewContainerMatrix.postTranslate(
- 0f, expandingFromBubble.getTranslationY()
- - expandingFromBubbleAt);
- } else {
- mExpandedViewContainerMatrix.postTranslate(
- expandingFromBubble.getTranslationX()
- - expandingFromBubbleAt, 0f);
- }
- }
-
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-
- // Hide early so we don't have a tiny little expanded view still visible at the
- // end of the scale animation.
- if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) {
- mExpandedViewContainer.setVisibility(View.INVISIBLE);
- }
})
.withEndActions(() -> {
final BubbleViewProvider previouslySelected = mExpandedBubble;
@@ -1882,7 +1992,7 @@
updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation();
if (previouslySelected != null) {
- previouslySelected.setContentVisibility(false);
+ previouslySelected.setTaskViewVisibility(false);
}
if (mPositioner.showingInTaskbar()) {
@@ -1903,22 +2013,21 @@
// The surface contains a screenshot of the animating out bubble, so we just need to animate
// it out (and then release the GraphicBuffer).
PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
- PhysicsAnimator animator = PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
- .spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig)
- .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
- .withEndActions(this::releaseAnimatingOutBubbleBuffer);
+
+ mAnimatingOutSurfaceAlphaAnimator.reverse();
+ mExpandedViewAlphaAnimator.start();
if (mPositioner.showBubblesVertically()) {
float translationX = mStackAnimationController.isStackOnLeftSide()
? mAnimatingOutSurfaceContainer.getTranslationX() + mBubbleSize * 2
: mAnimatingOutSurfaceContainer.getTranslationX();
- animator.spring(DynamicAnimation.TRANSLATION_X,
- translationX,
- mTranslateSpringConfig)
+ PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
+ .spring(DynamicAnimation.TRANSLATION_X, translationX, mTranslateSpringConfig)
.start();
} else {
- animator.spring(DynamicAnimation.TRANSLATION_Y,
- mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2,
+ PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
+ .spring(DynamicAnimation.TRANSLATION_Y,
+ mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize,
mTranslateSpringConfig)
.start();
}
@@ -1947,7 +2056,8 @@
pivotX, pivotY);
} else {
mExpandedViewContainerMatrix.setScale(
- 0f, 0f,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
expandingFromBubbleDestination + mBubbleSize / 2f,
getExpandedViewY());
}
@@ -1972,10 +2082,7 @@
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
})
.withEndActions(() -> {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
- }
-
+ mExpandedViewHidden = false;
mIsBubbleSwitchAnimating = false;
})
.start();
@@ -2489,6 +2596,7 @@
&& mExpandedBubble.getExpandedView() != null) {
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
bev.setContentVisibility(false);
+ bev.setAlphaAnimating(!mIsExpansionAnimating);
mExpandedViewContainerMatrix.setScaleX(0f);
mExpandedViewContainerMatrix.setScaleY(0f);
mExpandedViewContainerMatrix.setTranslate(0f, 0f);
@@ -2586,7 +2694,14 @@
mAnimatingOutBubbleBuffer.getHardwareBuffer(),
mAnimatingOutBubbleBuffer.getColorSpace());
- mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true)));
+ mAnimatingOutSurfaceView.setAlpha(1f);
+ mExpandedViewContainer.setVisibility(View.GONE);
+
+ mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+ post(() -> {
+ onComplete.accept(true);
+ });
+ });
});
}
@@ -2618,7 +2733,9 @@
}
}
mExpandedViewContainer.setPadding(leftPadding, 0, rightPadding, 0);
- mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
+ if (mIsExpansionAnimating) {
+ mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
+ }
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedViewContainer.setTranslationY(getExpandedViewY());
mExpandedViewContainer.setTranslationX(0f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index ec900be..da4259c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -29,7 +29,16 @@
public interface BubbleViewProvider {
@Nullable BubbleExpandedView getExpandedView();
- void setContentVisibility(boolean visible);
+ /**
+ * Sets the alpha of the expanded view content. This will be applied to both the expanded view
+ * container itself (the manage button, etc.) as well as the TaskView within it.
+ */
+ void setExpandedContentAlpha(float alpha);
+
+ /**
+ * Sets whether the contents of the bubble's TaskView should be visible.
+ */
+ void setTaskViewVisibility(boolean visible);
@Nullable View getIconView();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 728f60d..87f0c25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -39,6 +39,7 @@
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
@@ -55,6 +56,7 @@
private final ParentContainerCallbacks mParentContainerCallbacks;
private Context mContext;
private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mLeash;
private boolean mResizingSplits;
private final String mWindowName;
@@ -88,7 +90,15 @@
@Override
protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
- mParentContainerCallbacks.attachToParentSurface(b);
+ // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName(TAG)
+ .setHidden(false)
+ .setCallsite("SplitWindowManager#attachToParentSurface");
+ mParentContainerCallbacks.attachToParentSurface(builder);
+ mLeash = builder.build();
+ b.setParent(mLeash);
}
/** Inflates {@link DividerView} on to the root surface. */
@@ -118,9 +128,15 @@
* hierarchy.
*/
void release() {
- if (mViewHost == null) return;
- mViewHost.release();
- mViewHost = null;
+ if (mViewHost != null){
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mLeash != null) {
+ new SurfaceControl.Transaction().remove(mLeash).apply();
+ mLeash = null;
+ }
}
void setResizingSplits(boolean resizing) {
@@ -139,6 +155,6 @@
*/
@Nullable
SurfaceControl getSurfaceControl() {
- return mViewHost == null ? null : getSurfaceControl(mViewHost.getWindowToken());
+ return mLeash;
}
}
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/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/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/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 177646b..7ca5693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -106,6 +106,8 @@
/** Removes the split-screen stages. */
void exitSplitScreen();
+ /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide);
/** Gets the stage bounds. */
void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index bbad36d..b0167af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -126,6 +126,10 @@
mStageCoordinator.exitSplitScreen();
}
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ }
+
public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
}
@@ -292,6 +296,13 @@
}
@Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
+ }
+
+ @Override
public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
try {
mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 176852b..e44c820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -79,6 +79,7 @@
private DisplayAreaInfo mDisplayAreaInfo;
private final Context mContext;
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private boolean mExitSplitScreenOnHide = true;
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) {
@@ -113,7 +114,7 @@
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SplitScreen.StagePosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSideStagePosition = sideStagePosition;
+ setSideStagePosition(sideStagePosition);
mMainStage.activate(getMainStageBounds(), wct);
mSideStage.addTask(task, getSideStageBounds(), wct);
mTaskOrganizer.applyTransaction(wct);
@@ -144,14 +145,18 @@
}
void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
+ if (mSideStagePosition == sideStagePosition) return;
+
mSideStagePosition = sideStagePosition;
if (mSideStageListener.mVisible) {
onStageVisibilityChanged(mSideStageListener);
}
+
+ sendOnStagePositionChanged();
}
void setSideStageVisibility(boolean visible) {
- if (!mSideStageListener.mVisible == visible) return;
+ if (mSideStageListener.mVisible == visible) return;
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStage.setVisibility(visible, wct);
@@ -162,6 +167,10 @@
exitSplitScreen(null /* childrenToTop */);
}
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mExitSplitScreenOnHide = exitSplitScreenOnHide;
+ }
+
private void exitSplitScreen(StageTaskListener childrenToTop) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
@@ -202,6 +211,14 @@
mListeners.remove(listener);
}
+ private void sendOnStagePositionChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ }
+ }
+
private void onStageChildTaskStatusChanged(
StageListenerImpl stageListener, int taskId, boolean present) {
@@ -251,7 +268,7 @@
}
}
- if (!mainStageVisible && !sideStageVisible) {
+ if (mExitSplitScreenOnHide && !mainStageVisible && !sideStageVisible) {
// Exit split-screen if both stage are not visible.
// TODO: This is only a temporary request from UX and is likely to be removed soon...
exitSplitScreen();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt
new file mode 100644
index 0000000..1869d83
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("Utils")
+package com.android.wm.shell.flicker
+
+import android.app.ActivityTaskManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+
+fun removeAllTasksButHome() {
+ val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
+ ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
+ ACTIVITY_TYPE_UNDEFINED)
+ val atm = ActivityTaskManager.getService()
+ atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index 7ec22bb..cac46fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -32,13 +32,12 @@
/**
* Opens the IME and wait for it to be displayed
*
- * @param device UIDevice instance to interact with the device
* @param wmHelper Helper used to wait for WindowManager states
*/
@JvmOverloads
- open fun openIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
+ open fun openIME(wmHelper: WindowManagerStateHelper? = null) {
if (!isTelevision) {
- val editText = device.wait(
+ val editText = uiDevice.wait(
Until.findObject(By.res(getPackage(), "plain_text_input")),
FIND_TIMEOUT)
@@ -47,7 +46,7 @@
"was left in an unknown state (e.g. in split screen)"
}
editText.click()
- waitAndAssertIMEShown(device, wmHelper)
+ waitAndAssertIMEShown(uiDevice, wmHelper)
} else {
// If we do the same thing as above - editText.click() - on TV, that's going to force TV
// into the touch mode. We really don't want that.
@@ -69,16 +68,15 @@
/**
* Opens the IME and wait for it to be gone
*
- * @param device UIDevice instance to interact with the device
* @param wmHelper Helper used to wait for WindowManager states
*/
@JvmOverloads
- open fun closeIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
+ open fun closeIME(wmHelper: WindowManagerStateHelper? = null) {
if (!isTelevision) {
- device.pressBack()
+ uiDevice.pressBack()
// Using only the AccessibilityInfo it is not possible to identify if the IME is active
if (wmHelper == null) {
- device.waitForIdle()
+ uiDevice.waitForIdle()
} else {
require(wmHelper.waitImeWindowGone()) { "IME did did not close" }
}
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 b90e865..111362a 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
@@ -17,17 +17,20 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
+import android.graphics.Point
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.os.SystemClock
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.flicker.helpers.hasPipWindow
+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
+import com.android.wm.shell.flicker.pip.waitPipWindowGone
+import com.android.wm.shell.flicker.pip.waitPipWindowShown
import com.android.wm.shell.flicker.testapp.Components
-import org.junit.Assert.fail
class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
@@ -55,6 +58,17 @@
}
}
+ /** {@inheritDoc} */
+ override fun launchViaIntent(
+ wmHelper: WindowManagerStateHelper,
+ expectedWindowName: String,
+ action: String?,
+ stringExtras: Map<String, String>
+ ) {
+ super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
+ wmHelper.waitPipWindowShown()
+ }
+
private fun focusOnObject(selector: BySelector): Boolean {
// We expect all the focusable UI elements to be arranged in a way so that it is possible
// to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
@@ -69,16 +83,12 @@
return false
}
- fun clickEnterPipButton() {
+ @JvmOverloads
+ fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) {
clickObject(ENTER_PIP_BUTTON_ID)
- // TODO(b/172321238): remove this check once hasPipWindow is fixed on TVs
- if (!isTelevision) {
- uiDevice.hasPipWindow()
- } else {
- // Simply wait for 3 seconds
- SystemClock.sleep(3_000)
- }
+ // Wait on WMHelper or simply wait for 3 seconds
+ wmHelper?.waitPipWindowShown() ?: SystemClock.sleep(3_000)
}
fun clickStartMediaSessionButton() {
@@ -97,16 +107,75 @@
fun stopMedia() = mediaController?.transportControls?.stop()
?: error("No active media session found")
+ @Deprecated("Use PipAppHelper.closePipWindow(wmHelper) instead",
+ ReplaceWith("closePipWindow(wmHelper)"))
fun closePipWindow() {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
uiDevice.closePipWindow()
}
+ }
- if (!waitUntilClosed()) {
- fail("Couldn't close Pip")
+ /**
+ * Expands the pip window and dismisses it by clicking on the X button.
+ *
+ * Note, currently the View coordinates reported by the accessibility are relative to
+ * the window, so the correct coordinates need to be calculated
+ *
+ * For example, in a PIP window located at Rect(508, 1444 - 1036, 1741), the
+ * dismiss button coordinates are shown as Rect(650, 0 - 782, 132), with center in
+ * Point(716, 66), instead of Point(970, 1403)
+ *
+ * See b/179337864
+ */
+ fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+ if (isTelevision) {
+ uiDevice.closeTvPipWindow()
+ } else {
+ expandPipWindow(wmHelper)
+ val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss"))
+ requireNotNull(exitPipObject) { "PIP window dismiss button not found" }
+ val coordinatesInWindow = exitPipObject.visibleBounds
+ val windowOffset = wmHelper.getWindowRegion(component).bounds
+ val newCoordinates = Point(windowOffset.left + coordinatesInWindow.centerX(),
+ windowOffset.top + coordinatesInWindow.centerY())
+ uiDevice.click(newCoordinates.x, newCoordinates.y)
}
+
+ // Wait for animation to complete.
+ wmHelper.waitPipWindowGone()
+ wmHelper.waitForHomeActivityVisible()
+ }
+
+ /**
+ * Click once on the PIP window to expand it
+ */
+ fun expandPipWindow(wmHelper: WindowManagerStateHelper) {
+ val windowRegion = wmHelper.getWindowRegion(component)
+ require(!windowRegion.isEmpty) {
+ "Unable to find a PIP window in the current state"
+ }
+ val windowRect = windowRegion.bounds
+ uiDevice.click(windowRect.centerX(), windowRect.centerY())
+ // Ensure WindowManagerService wait until all animations have completed
+ wmHelper.waitForAppTransitionIdle()
+ mInstrumentation.uiAutomation.syncInputTransactions()
+ }
+
+ /**
+ * Double click on the PIP window to reopen to app
+ */
+ fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
+ val windowRegion = wmHelper.getWindowRegion(component)
+ require(!windowRegion.isEmpty) {
+ "Unable to find a PIP window in the current state"
+ }
+ val windowRect = windowRegion.bounds
+ uiDevice.click(windowRect.centerX(), windowRect.centerY())
+ uiDevice.click(windowRect.centerX(), windowRect.centerY())
+ wmHelper.waitPipWindowGone()
+ wmHelper.waitForAppTransitionIdle()
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
index 2015f49..bc42d5e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
@@ -16,11 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.app.ActivityTaskManager
-import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
-import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.os.SystemClock
import com.android.wm.shell.flicker.NonRotationTestBase
@@ -29,14 +24,6 @@
rotation: Int
) : NonRotationTestBase(rotationName, rotation) {
companion object {
- fun removeAllTasksButHome() {
- val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
- ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
- ACTIVITY_TYPE_UNDEFINED)
- val atm = ActivityTaskManager.getService()
- atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
- }
-
fun waitForAnimationComplete() {
// TODO: UiDevice doesn't have reliable way to wait for the completion of animation.
// Consider to introduce WindowManagerStateHelper to access Activity state.
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 5a3d18d..a14b46e 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,21 +16,21 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Presubmit
+import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runFlicker
+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.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,82 +39,67 @@
* Test Pip launch and exit.
* To run this test: `atest WMShellFlickerTests:EnterExitPipTest`
*/
-@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class EnterExitPipTest(
- rotationName: String,
- rotation: Int
-) : AppTestBase(rotationName, rotation) {
- private val pipApp = PipAppHelper(instrumentation)
- private val testApp = FixedAppHelper(instrumentation)
-
- @Test
- fun testDisplayMetricsPinUnpin() {
- runFlicker(instrumentation) {
- withTestName { "testDisplayMetricsPinUnpin" }
- setup {
- test {
- removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
- testApp.launchViaIntent()
- waitForAnimationComplete()
- }
- }
- transitions {
- // This will bring PipApp to fullscreen
- pipApp.launchViaIntent()
- waitForAnimationComplete()
- }
- teardown {
- test {
- removeAllTasksButHome()
- }
- }
- assertions {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- windowManagerTrace {
- all("pipApp must remain inside visible bounds") {
- coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
- }
- all("Initially shows both app windows then pipApp hides testApp") {
- showsAppWindow(testApp.defaultWindowName)
- .showsAppWindowOnTop(pipApp.defaultWindowName)
- .then()
- .hidesAppWindow(testApp.defaultWindowName)
- }
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- layersTrace {
- all("Initially shows both app layers then pipApp hides testApp") {
- showsLayer(testApp.defaultWindowName)
- .showsLayer(pipApp.defaultWindowName)
- .then()
- .hidesLayer(testApp.defaultWindowName)
- }
- start("testApp covers the fullscreen, pipApp remains inside display") {
- hasVisibleRegion(testApp.defaultWindowName, displayBounds)
- coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
- }
- end("pipApp covers the fullscreen") {
- hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
- }
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- }
- }
- }
- }
-
- companion object {
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ fun getParams(): List<Array<Any>> {
+ val testApp = FixedAppHelper(instrumentation)
+ val baseConfig = getTransitionLaunch(eachRun = true)
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ setup {
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.launchViaIntent(wmHelper)
+ }
+ assertions {
+ val displayBounds = WindowUtils.getDisplayBounds(configuration.startRotation)
+ presubmit {
+ windowManagerTrace {
+ all("pipApp must remain inside visible bounds") {
+ coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
+ }
+ all("Initially shows both app windows then pipApp hides testApp") {
+ showsAppWindow(testApp.defaultWindowName)
+ .showsAppWindowOnTop(pipApp.defaultWindowName)
+ .then()
+ .hidesAppWindow(testApp.defaultWindowName)
+ }
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ layersTrace {
+ all("Initially shows both app layers then pipApp hides testApp") {
+ showsLayer(testApp.defaultWindowName)
+ .showsLayer(pipApp.defaultWindowName)
+ .then()
+ .hidesLayer(testApp.defaultWindowName)
+ }
+ start("testApp covers the fullscreen, pipApp remains inside display") {
+ hasVisibleRegion(testApp.defaultWindowName, displayBounds)
+ coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
+ }
+ end("pipApp covers the fullscreen") {
+ hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
+ }
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ }
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5)
}
}
}
\ No newline at end of file
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 af62eb9..99a40da 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,18 +16,13 @@
package com.android.wm.shell.flicker.pip
+import android.os.Bundle
import android.view.Surface
-import androidx.test.filters.FlakyTest
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.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.expandPipWindow
-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.dsl.FlickerBuilder
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -35,9 +30,7 @@
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
-import com.android.wm.shell.flicker.helpers.PipAppHelper
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -50,80 +43,58 @@
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
class EnterPipTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
- companion object {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val testApp = PipAppHelper(instrumentation)
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
- supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
- withTestName { buildTestTag("enterPip", testApp, configuration) }
- repeat { configuration.repetitions }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- device.pressHome()
- testApp.launchViaIntent(wmHelper)
- this.setRotation(configuration.startRotation)
- }
- }
- teardown {
- eachRun {
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- test {
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
- }
- }
- transitions {
- testApp.clickEnterPipButton()
- device.expandPipWindow()
- }
- assertions {
+ val baseConfig = getTransitionLaunch(
+ eachRun = true, stringExtras = emptyMap())
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ transitions {
+ pipApp.clickEnterPipButton()
+ pipApp.expandPipWindow(wmHelper)
+ }
+ assertions {
+ presubmit {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
all("pipWindowBecomesVisible") {
- this.showsAppWindow(testApp.`package`)
- .then()
- .showsAppWindow(PIP_WINDOW_TITLE)
+ this.showsAppWindow(pipApp.defaultWindowName)
}
}
layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
- enabled = false)
- navBarLayerRotatesAndScales(configuration.startRotation,
- Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
}
layersTrace {
all("pipLayerBecomesVisible") {
- this.showsLayer(testApp.launcherName)
- .then()
- .showsLayer(PIP_WINDOW_TITLE)
+ this.showsLayer(pipApp.launcherName)
}
}
}
+
+ flaky {
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ }
+ }
}
+ }
+
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5)
}
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
new file mode 100644
index 0000000..eaaa2f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.flicker.pip
+
+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.helpers.WindowUtils
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
+import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterPipToOtherOrientationTest(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
+ private val testApp = FixedAppHelper(instrumentation)
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+ supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5) { configuration ->
+ setupAndTeardown(this, configuration)
+
+ setup {
+ eachRun {
+ // Launch a portrait only app on the fullscreen stack
+ testApp.launchViaIntent(wmHelper, stringExtras = mapOf(
+ EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
+ // Launch the PiP activity fixed as landscape
+ pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
+ EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+ }
+ }
+ teardown {
+ eachRun {
+ pipApp.exit()
+ testApp.exit()
+ }
+ }
+ transitions {
+ // Enter PiP, and assert that the PiP is within bounds now that the device is back
+ // in portrait
+ broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
+ wmHelper.waitPipWindowShown()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ assertions {
+ val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+ val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+
+ presubmit {
+ windowManagerTrace {
+ all("pipApp window is always on top") {
+ showsAppWindowOnTop(pipApp.defaultWindowName)
+ }
+ start("pipApp window hides testApp") {
+ isInvisible(testApp.defaultWindowName)
+ }
+ end("testApp windows is shown") {
+ isVisible(testApp.defaultWindowName)
+ }
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+
+ layersTrace {
+ start("pipApp layer hides testApp") {
+ hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
+ isInvisible(testApp.defaultWindowName)
+ }
+ }
+ }
+
+ flaky {
+ layersTrace {
+ end("testApp layer covers fullscreen") {
+ hasVisibleRegion(testApp.defaultWindowName, endingBounds)
+ }
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
new file mode 100644
index 0000000..707d28d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject
+import com.android.server.wm.traces.common.windowmanager.WindowManagerState
+import com.android.server.wm.traces.parser.toWindowName
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.google.common.truth.Truth
+
+inline val WindowManagerState.pinnedWindows
+ get() = visibleWindows
+ .filter { it.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED }
+
+/**
+ * Checks if the state has any window in PIP mode
+ */
+fun WindowManagerState.hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
+
+/**
+ * Checks that an activity [activity] is in PIP mode
+ */
+fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean {
+ val windowName = activity.toWindowName()
+ return pinnedWindows.any { it.title == windowName }
+}
+
+/**
+ * Asserts that an activity [activity] exists and is in PIP mode
+ */
+fun WindowManagerStateSubject.isInPipMode(
+ activity: ComponentName
+): WindowManagerStateSubject = apply {
+ val windowName = activity.toWindowName()
+ hasWindow(windowName)
+ val pinnedWindows = wmState.pinnedWindows
+ .map { it.title }
+ Truth.assertWithMessage("Window not in PIP mode")
+ .that(pinnedWindows)
+ .contains(windowName)
+}
+
+/**
+ * Waits until the state has a window in PIP mode, i.e., with
+ * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
+ */
+fun WindowManagerStateHelper.waitPipWindowShown(): Boolean =
+ waitFor("PIP window shown") {
+ val result = it.wmState.hasPipWindow()
+ result
+ }
+
+/**
+ * Waits until the state doesn't have a window in PIP mode, i.e., with
+ * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
+ */
+fun WindowManagerStateHelper.waitPipWindowGone(): Boolean =
+ waitFor("PIP window gone") {
+ val result = !it.wmState.hasPipWindow()
+ result
+ }
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 c21b594..7576e24 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,21 +16,19 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Presubmit
+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.dsl.runWithFlicker
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.startRotation
import com.android.wm.shell.flicker.IME_WINDOW_NAME
import com.android.wm.shell.flicker.helpers.ImeAppHelper
-import com.android.wm.shell.flicker.testapp.Components
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,114 +37,61 @@
* Test Pip launch.
* To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
*/
-@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipKeyboardTest(
- rotationName: String,
- rotation: Int
-) : PipTestBase(rotationName, rotation) {
- private val keyboardApp = ImeAppHelper(instrumentation)
- private val keyboardComponent = Components.ImeActivity.COMPONENT
- private val helper = WindowManagerStateHelper()
-
- private val keyboardScenario: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- repeat { TEST_REPETITIONS }
- // disable layer tracing
- withLayerTracing { null }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.pressHome()
- // launch our target pip app
- testApp.launchViaIntent(wmHelper)
- this.setRotation(rotation)
- testApp.clickEnterPipButton()
- // open an app with an input field and a keyboard
- // UiAutomator doesn't support to launch the multiple Activities in a task.
- // So use launchActivity() for the Keyboard Activity.
- keyboardApp.launchViaIntent()
- helper.waitForAppTransitionIdle()
- helper.waitForFullScreenApp(keyboardComponent)
- }
- }
- teardown {
- test {
- keyboardApp.exit()
-
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-
- /** Ensure the pip window remains visible throughout any keyboard interactions. */
- @Test
- fun pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose() {
- val testTag = "pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose"
- runWithFlicker(keyboardScenario) {
- withTestName { testTag }
- transitions {
- // open the soft keyboard
- keyboardApp.openIME(device, wmHelper)
- helper.waitImeWindowShown()
-
- // then close it again
- keyboardApp.closeIME(device, wmHelper)
- helper.waitImeWindowGone()
- }
- assertions {
- windowManagerTrace {
- all("PiP window must remain inside visible bounds") {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- coversAtMostRegion(testApp.defaultWindowName, displayBounds)
- }
- }
- }
- }
- }
-
- /** Ensure the pip window does not obscure the keyboard. */
- @Test
- fun pipWindow_doesNotObscure_keyboard() {
- val testTag = "pipWindow_doesNotObscure_keyboard"
- runWithFlicker(keyboardScenario) {
- withTestName { testTag }
- transitions {
- // open the soft keyboard
- keyboardApp.openIME(device, wmHelper)
- helper.waitImeWindowShown()
- }
- teardown {
- eachRun {
- // close the keyboard
- keyboardApp.closeIME(device, wmHelper)
- helper.waitImeWindowGone()
- }
- }
- assertions {
- windowManagerTrace {
- end("imeWindowAboveApp") {
- isAboveWindow(IME_WINDOW_NAME, testApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- private const val TEST_REPETITIONS = 5
+class PipKeyboardTest(testSpec: FlickerTestRunnerFactory.TestSpec) : FlickerTestRunner(testSpec) {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
+ private const val TAG_IME_VISIBLE = "imeIsVisible"
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ val imeApp = ImeAppHelper(instrumentation)
+ val baseConfig = getTransitionLaunch(eachRun = false)
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ setup {
+ test {
+ imeApp.launchViaIntent(wmHelper)
+ setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ test {
+ imeApp.exit()
+ setRotation(Surface.ROTATION_0)
+ }
+ }
+ transitions {
+ // open the soft keyboard
+ imeApp.openIME(wmHelper)
+ createTag(TAG_IME_VISIBLE)
+
+ // then close it again
+ imeApp.closeIME(wmHelper)
+ }
+ assertions {
+ presubmit {
+ windowManagerTrace {
+ // Ensure the pip window remains visible throughout
+ // any keyboard interactions
+ all("pipInVisibleBounds") {
+ val displayBounds = WindowUtils.getDisplayBounds(
+ configuration.startRotation)
+ coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
+ }
+ // Ensure that the pip window does not obscure the keyboard
+ tag(TAG_IME_VISIBLE) {
+ isAboveWindow(IME_WINDOW_NAME, pipApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+ baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index e579096..f10bd7f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -32,6 +32,7 @@
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.removeAllTasksButHome
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
deleted file mode 100644
index 5e0760c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
+++ /dev/null
@@ -1,205 +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.wm.shell.flicker.pip
-
-import android.content.Intent
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import org.junit.Assert.assertEquals
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test Pip with orientation changes.
- * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipOrientationTest(
- rotationName: String,
- rotation: Int
-) : AppTestBase(rotationName, rotation) {
- // Helper class to process test actions by broadcast.
- private inner class BroadcastActionTrigger {
- private fun createIntentWithAction(broadcastAction: String): Intent {
- return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- }
- fun doAction(broadcastAction: String) {
- instrumentation.getContext().sendBroadcast(createIntentWithAction(broadcastAction))
- }
- fun requestOrientationForPip(orientation: Int) {
- instrumentation.getContext()
- .sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
- .putExtra(EXTRA_PIP_ORIENTATION, orientation.toString()))
- }
- }
- private val broadcastActionTrigger = BroadcastActionTrigger()
-
- // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- private val ORIENTATION_LANDSCAPE = 0
- // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- private val ORIENTATION_PORTRAIT = 1
-
- private val testApp = FixedAppHelper(instrumentation)
- private val pipApp = PipAppHelper(instrumentation)
-
- @Test
- fun testEnterPipToOtherOrientation() {
- runFlicker(instrumentation) {
- withTestName { "testEnterPipToOtherOrientation" }
- setup {
- test {
- removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- // Launch a portrait only app on the fullscreen stack
- testApp.launchViaIntent(stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
- waitForAnimationComplete()
- // Launch the PiP activity fixed as landscape
- pipApp.launchViaIntent(stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
- waitForAnimationComplete()
- }
- }
- transitions {
- // Enter PiP, and assert that the PiP is within bounds now that the device is back
- // in portrait
- broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
- waitForAnimationComplete()
- }
- teardown {
- test {
- removeAllTasksButHome()
- }
- }
- assertions {
- windowManagerTrace {
- all("pipApp window is always on top") {
- showsAppWindowOnTop(pipApp.defaultWindowName)
- }
- start("pipApp window hides testApp") {
- isInvisible(testApp.defaultWindowName)
- }
- end("testApp windows is shown") {
- isVisible(testApp.defaultWindowName)
- }
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- layersTrace {
- val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
- val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
- start("pipApp layer hides testApp") {
- hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
- isInvisible(testApp.defaultWindowName)
- }
- end("testApp layer covers fullscreen", enabled = false) {
- hasVisibleRegion(testApp.defaultWindowName, endingBounds)
- }
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible(bugId = 140855415)
- }
- }
- }
- }
-
- @Test
- fun testSetRequestedOrientationWhilePinned() {
- runFlicker(instrumentation) {
- withTestName { "testSetRequestedOrientationWhilePinned" }
- setup {
- test {
- removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- // Launch the PiP activity fixed as landscape
- pipApp.launchViaIntent(stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
- EXTRA_ENTER_PIP to "true"))
- waitForAnimationComplete()
- assertEquals(Surface.ROTATION_0, device.displayRotation)
- }
- }
- transitions {
- // Request that the orientation is set to landscape
- broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
-
- // Launch the activity back into fullscreen and ensure that it is now in landscape
- pipApp.launchViaIntent()
- waitForAnimationComplete()
- assertEquals(Surface.ROTATION_90, device.displayRotation)
- }
- teardown {
- test {
- removeAllTasksButHome()
- }
- }
- assertions {
- val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
- val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
- windowManagerTrace {
- start("PIP window must remain inside display") {
- coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
- }
- end("pipApp shows on top") {
- showsAppWindowOnTop(pipApp.defaultWindowName)
- }
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- layersTrace {
- start("PIP layer must remain inside display") {
- coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
- }
- end("pipApp layer covers fullscreen") {
- hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
- }
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible(bugId = 140855415)
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
\ No newline at end of file
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 a00c5f4..adab5e8 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,21 +16,18 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Presubmit
+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.buildTestTag
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -38,7 +35,6 @@
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -48,80 +44,77 @@
* Test Pip Stack in bounds after rotations.
* To run this test: `atest WMShellFlickerTests:PipRotationTest`
*/
-@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class PipRotationTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
- companion object {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val testApp = FixedAppHelper(instrumentation)
- val pipApp = PipAppHelper(instrumentation)
- return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) {
- configuration ->
- withTestName { buildTestTag("PipRotationTest", testApp, configuration) }
- repeat { configuration.repetitions }
- setup {
- test {
- AppTestBase.removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- pipApp.launchViaIntent(stringExtras = mapOf(
- EXTRA_ENTER_PIP to "true"))
- testApp.launchViaIntent()
- AppTestBase.waitForAnimationComplete()
- }
- eachRun {
- setRotation(configuration.startRotation)
- }
+ val fixedApp = FixedAppHelper(instrumentation)
+ val baseConfig = getTransitionLaunch(eachRun = false)
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ setup {
+ test {
+ fixedApp.launchViaIntent(wmHelper)
+ }
+ eachRun {
+ setRotation(configuration.startRotation)
+ }
+ }
+ transitions {
+ setRotation(configuration.endRotation)
+ }
+ teardown {
+ eachRun {
+ setRotation(Surface.ROTATION_0)
+ }
+ }
+ assertions {
+ val startingBounds = WindowUtils.getDisplayBounds(configuration.startRotation)
+ val endingBounds = WindowUtils.getDisplayBounds(configuration.endRotation)
+
+ presubmit {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
}
- transitions {
- setRotation(configuration.endRotation)
+
+ layersTrace {
+ noUncoveredRegions(configuration.startRotation,
+ configuration.endRotation, allStates = false)
}
- teardown {
- eachRun {
- setRotation(Surface.ROTATION_0)
+ }
+
+ flaky {
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ configuration.endRotation, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ configuration.endRotation, bugId = 140855415)
+
+ start("appLayerRotates_StartingBounds", bugId = 140855415) {
+ hasVisibleRegion(fixedApp.defaultWindowName, startingBounds)
+ coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
}
- test {
- AppTestBase.removeAllTasksButHome()
- }
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible(bugId = 140855415)
- noUncoveredRegions(configuration.startRotation,
- configuration.endRotation, allStates = false)
- navBarLayerRotatesAndScales(configuration.startRotation,
- configuration.endRotation, bugId = 140855415)
- statusBarLayerRotatesScales(configuration.startRotation,
- configuration.endRotation, bugId = 140855415)
- }
- layersTrace {
- val startingBounds = WindowUtils.getDisplayBounds(
- configuration.startRotation)
- val endingBounds = WindowUtils.getDisplayBounds(
- configuration.endRotation)
- start("appLayerRotates_StartingBounds", bugId = 140855415) {
- hasVisibleRegion(testApp.defaultWindowName, startingBounds)
- coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
- }
- end("appLayerRotates_EndingBounds", bugId = 140855415) {
- hasVisibleRegion(testApp.defaultWindowName, endingBounds)
- coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
- }
+ end("appLayerRotates_EndingBounds", bugId = 140855415) {
+ hasVisibleRegion(fixedApp.defaultWindowName, endingBounds)
+ coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
}
}
}
+ }
+ }
+
+ return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
+ baseConfig, testSpec,
+ supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+ repetitions = 5)
}
}
}
\ No newline at end of file
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 3e7eb134..4b826ff 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,29 +16,23 @@
package com.android.wm.shell.flicker.pip
+import android.os.Bundle
import android.view.Surface
-import androidx.test.filters.FlakyTest
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.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.expandPipWindow
-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.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.helpers.PipAppHelper
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -51,48 +45,30 @@
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
class PipToAppTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
- companion object {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val testApp = PipAppHelper(instrumentation)
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
- supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
- withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
- repeat { configuration.repetitions }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.pressHome()
- testApp.launchViaIntent(wmHelper)
- }
- eachRun {
- this.setRotation(configuration.startRotation)
- testApp.clickEnterPipButton()
- device.hasPipWindow()
- }
+ val baseConfig = getTransitionLaunch(eachRun = true)
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ setup {
+ eachRun {
+ this.setRotation(configuration.startRotation)
}
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- }
- test {
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
- testApp.exit()
- }
+ }
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
}
- transitions {
- device.expandPipWindow()
- device.waitForIdle()
- }
- assertions {
+ }
+ transitions {
+ pipApp.expandPipWindowToApp(wmHelper)
+ }
+ assertions {
+ presubmit {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
@@ -100,34 +76,42 @@
all("appReplacesPipWindow") {
this.showsAppWindow(PIP_WINDOW_TITLE)
.then()
- .showsAppWindowOnTop(testApp.launcherName)
+ .showsAppWindowOnTop(pipApp.launcherName)
}
}
layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
- enabled = false)
- navBarLayerRotatesAndScales(configuration.startRotation,
- Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
all("appReplacesPipLayer") {
this.showsLayer(PIP_WINDOW_TITLE)
.then()
- .showsLayer(testApp.launcherName)
+ .showsLayer(pipApp.launcherName)
}
}
+ }
+
+ flaky {
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ }
eventLog {
focusChanges(
- "NexusLauncherActivity", testApp.launcherName,
+ "NexusLauncherActivity", pipApp.launcherName,
"NexusLauncherActivity", bugId = 151179149)
}
}
}
+ }
+
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ 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 5d3bc13..62e8221 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,28 +16,23 @@
package com.android.wm.shell.flicker.pip
+import android.os.Bundle
import android.view.Surface
-import androidx.test.filters.FlakyTest
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.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.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.helpers.PipAppHelper
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -50,50 +45,30 @@
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
class PipToHomeTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
- companion object {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val testApp = PipAppHelper(instrumentation)
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
- supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
- withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
- repeat { configuration.repetitions }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.pressHome()
- }
- eachRun {
- testApp.launchViaIntent(wmHelper)
- this.setRotation(configuration.startRotation)
- testApp.clickEnterPipButton()
- device.hasPipWindow()
- }
+ val baseConfig = getTransitionLaunch(eachRun = true)
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ setup {
+ eachRun {
+ this.setRotation(configuration.startRotation)
}
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
- }
- test {
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
- testApp.exit()
- }
+ }
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
}
- transitions {
- testApp.closePipWindow()
- }
- assertions {
+ }
+ transitions {
+ pipApp.closePipWindow(wmHelper)
+ }
+ assertions {
+ presubmit {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
@@ -106,12 +81,7 @@
}
layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
- enabled = false)
- navBarLayerRotatesAndScales(configuration.startRotation,
- Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
@@ -121,13 +91,28 @@
.hidesLayer(PIP_WINDOW_TITLE)
}
}
+ }
+ postsubmit {
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0)
+ }
+ }
+
+ flaky {
eventLog {
- focusChanges(testApp.launcherName, "NexusLauncherActivity",
+ focusChanges(pipApp.launcherName, "NexusLauncherActivity",
bugId = 151179149)
}
}
}
+ }
+
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
}
\ No newline at end of file
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
new file mode 100644
index 0000000..eb7bae1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.flicker.pip
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.os.Bundle
+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
+import com.android.wm.shell.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.removeAllTasksButHome
+import com.android.wm.shell.flicker.testapp.Components
+
+abstract class PipTransitionBase(protected val instrumentation: Instrumentation) {
+ // Helper class to process test actions by broadcast.
+ protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
+ private fun createIntentWithAction(broadcastAction: String): Intent {
+ return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ }
+
+ fun doAction(broadcastAction: String) {
+ instrumentation.context
+ .sendBroadcast(createIntentWithAction(broadcastAction))
+ }
+
+ fun requestOrientationForPip(orientation: Int) {
+ instrumentation.context.sendBroadcast(
+ createIntentWithAction(Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION)
+ .putExtra(Components.PipActivity.EXTRA_PIP_ORIENTATION, orientation.toString())
+ )
+ }
+
+ companion object {
+ // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ @JvmStatic
+ val ORIENTATION_LANDSCAPE = 0
+
+ // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ @JvmStatic
+ val ORIENTATION_PORTRAIT = 1
+ }
+ }
+
+ protected val pipApp = PipAppHelper(instrumentation)
+ protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
+
+ /**
+ * Gets a configuration that handles basic setup and teardown of pip tests
+ */
+ protected val setupAndTeardown: FlickerBuilder.(Bundle) -> Unit
+ get() = { configuration ->
+ withTestName { buildTestTag(configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ removeAllTasksButHome()
+ device.wakeUpAndGoToHomeScreen()
+ }
+ }
+ teardown {
+ eachRun {
+ setRotation(Surface.ROTATION_0)
+ }
+ test {
+ removeAllTasksButHome()
+
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ pipApp.exit()
+ }
+ }
+ }
+
+ /**
+ * Gets a configuration that handles basic setup and teardown of pip tests and that
+ * launches the Pip app for test
+ *
+ * @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
+ */
+ @JvmOverloads
+ fun getTransitionLaunch(
+ eachRun: Boolean,
+ stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
+ ): FlickerBuilder.(Bundle) -> Unit {
+ return { configuration ->
+ setupAndTeardown(this, configuration)
+
+ setup {
+ test {
+ removeAllTasksButHome()
+ if (!eachRun) {
+ pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+ wmHelper.waitPipWindowShown()
+ }
+ }
+ eachRun {
+ if (eachRun) {
+ pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+ wmHelper.waitPipWindowShown()
+ }
+ }
+ }
+ teardown {
+ eachRun {
+ if (eachRun) {
+ pipApp.exit()
+ }
+ }
+ test {
+ if (!eachRun) {
+ pipApp.exit()
+ }
+ removeAllTasksButHome()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
new file mode 100644
index 0000000..c01bc94
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+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.helpers.WindowUtils
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import org.junit.Assert.assertEquals
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SetRequestedOrientationWhilePinnedTest(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+ supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 1) { configuration ->
+ setupAndTeardown(this, configuration)
+
+ setup {
+ eachRun {
+ // Launch the PiP activity fixed as landscape
+ pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
+ EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
+ EXTRA_ENTER_PIP to "true"))
+ }
+ }
+ teardown {
+ eachRun {
+ pipApp.exit()
+ }
+ }
+ transitions {
+ // Request that the orientation is set to landscape
+ broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
+
+ // Launch the activity back into fullscreen and
+ // ensure that it is now in landscape
+ pipApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(pipApp.component)
+ wmHelper.waitForRotation(Surface.ROTATION_90)
+ assertEquals(Surface.ROTATION_90, device.displayRotation)
+ }
+ assertions {
+ val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+ val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+ presubmit {
+ windowManagerTrace {
+ start("PIP window must remain inside display") {
+ coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
+ }
+ end("pipApp shows on top") {
+ showsAppWindowOnTop(pipApp.defaultWindowName)
+ }
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ layersTrace {
+ start("PIP layer must remain inside display") {
+ coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
+ }
+ end("pipApp layer covers fullscreen") {
+ hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
+ }
+ }
+ }
+
+ flaky {
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ 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/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 27be622..d5fee3f 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -35,7 +35,6 @@
public:
static DeviceInfo* get();
- static float getMaxRefreshRate() { return get()->mMaxRefreshRate; }
static int32_t getWidth() { return get()->mWidth; }
static int32_t getHeight() { return get()->mHeight; }
// Gets the density in density-independent pixels
@@ -45,7 +44,6 @@
static int64_t getAppOffset() { return get()->mAppVsyncOffsetNanos; }
// Sets the density in density-independent pixels
static void setDensity(float density) { sDensity.store(density); }
- static void setMaxRefreshRate(float refreshRate) { get()->mMaxRefreshRate = refreshRate; }
static void setWidth(int32_t width) { get()->mWidth = width; }
static void setHeight(int32_t height) { get()->mHeight = height; }
static void setRefreshRate(float refreshRate) {
@@ -91,7 +89,6 @@
SkColorType mWideColorType = SkColorType::kN32_SkColorType;
int mDisplaysSize = 0;
int mPhysicalDisplayIndex = -1;
- float mMaxRefreshRate = 60.0;
int32_t mWidth = 1080;
int32_t mHeight = 1920;
int64_t mVsyncPeriod = 16666666;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e798f2a..971a53a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -79,7 +79,6 @@
bool Properties::isolatedProcess = false;
int Properties::contextPriority = 0;
-int Properties::defaultRenderAhead = -1;
float Properties::defaultSdrWhitePoint = 200.f;
bool Properties::load() {
@@ -129,10 +128,6 @@
runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false);
- defaultRenderAhead = std::max(
- -1,
- std::min(2, base::GetIntProperty(PROPERTY_RENDERAHEAD, render_ahead().value_or(-1))));
-
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1639143..dcb79ba 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -162,8 +162,6 @@
*/
#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
-#define PROPERTY_RENDERAHEAD "debug.hwui.render_ahead"
-
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -247,8 +245,6 @@
static int contextPriority;
- static int defaultRenderAhead;
-
static float defaultSdrWhitePoint;
private:
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/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index a146b64..4966bfa 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -603,14 +603,12 @@
static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint physicalWidth,
jint physicalHeight, jfloat refreshRate,
- jfloat maxRefreshRate,
jint wideColorDataspace,
jlong appVsyncOffsetNanos,
jlong presentationDeadlineNanos) {
DeviceInfo::setWidth(physicalWidth);
DeviceInfo::setHeight(physicalHeight);
DeviceInfo::setRefreshRate(refreshRate);
- DeviceInfo::setMaxRefreshRate(maxRefreshRate);
DeviceInfo::setWideColorDataspace(static_cast<ADataSpace>(wideColorDataspace));
DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos);
DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
@@ -735,7 +733,7 @@
{"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
{"nSetDisplayDensityDpi", "(I)V",
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
- {"nInitDisplayInfo", "(IIFFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
+ {"nInitDisplayInfo", "(IIFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
{"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
};
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 3392dac..c8471a9 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -219,7 +219,7 @@
}
// Critical Native
-static jlong Font_getReleaseNativeFontFunc() {
+static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) {
return reinterpret_cast<jlong>(releaseFont);
}
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 8096479..b682135 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -101,13 +101,13 @@
}
// CriticalNative
-static jint FontFamily_getFontSize(jlong familyPtr) {
+static jint FontFamily_getFontSize(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr) {
FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr);
return family->family->getNumFonts();
}
// CriticalNative
-static jlong FontFamily_getFont(jlong familyPtr, jint index) {
+static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint index) {
FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr);
std::shared_ptr<minikin::Font> font = family->family->getFontRef(index);
return reinterpret_cast<jlong>(new FontWrapper(std::move(font)));
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 37a6ee7..65afcc3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -108,7 +108,6 @@
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
- setRenderAheadDepth(Properties::defaultRenderAhead);
}
CanvasContext::~CanvasContext() {
@@ -157,24 +156,17 @@
void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
ATRACE_CALL();
- if (mFixedRenderAhead) {
- mRenderAheadCapacity = mRenderAheadDepth;
- } else {
- if (DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
- mRenderAheadCapacity = 1;
- } else {
- mRenderAheadCapacity = 0;
- }
- }
-
if (window) {
+ int extraBuffers = 0;
+ native_window_get_extra_buffer_count(window, &extraBuffers);
+
mNativeSurface = std::make_unique<ReliableSurface>(window);
mNativeSurface->init();
if (enableTimeout) {
// TODO: Fix error handling & re-shorten timeout
ANativeWindow_setDequeueTimeout(window, 4000_ms);
}
- mNativeSurface->setExtraBufferCount(mRenderAheadCapacity);
+ mNativeSurface->setExtraBufferCount(extraBuffers);
} else {
mNativeSurface = nullptr;
}
@@ -441,24 +433,6 @@
mRenderThread.pushBackFrameCallback(this);
}
-void CanvasContext::setPresentTime() {
- int64_t presentTime = NATIVE_WINDOW_TIMESTAMP_AUTO;
- int renderAhead = 0;
- const auto frameIntervalNanos = mRenderThread.timeLord().frameIntervalNanos();
- if (mFixedRenderAhead) {
- renderAhead = std::min(mRenderAheadDepth, mRenderAheadCapacity);
- } else if (frameIntervalNanos < 15_ms) {
- renderAhead = std::min(1, static_cast<int>(mRenderAheadCapacity));
- }
-
- if (renderAhead) {
- presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) +
- (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() +
- (frameIntervalNanos / 2);
- }
- native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime);
-}
-
void CanvasContext::draw() {
SkRect dirty;
mDamageAccumulator.finish(&dirty);
@@ -478,8 +452,6 @@
mCurrentFrameInfo->markIssueDrawCommandsStart();
Frame frame = mRenderPipeline->getFrame();
- setPresentTime();
-
SkRect windowDirty = computeDirtyRect(frame, &dirty);
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
@@ -765,19 +737,6 @@
return width != mLastFrameWidth || height != mLastFrameHeight;
}
-void CanvasContext::setRenderAheadDepth(int renderAhead) {
- if (renderAhead > 2 || renderAhead < -1 || mNativeSurface) {
- return;
- }
- if (renderAhead == -1) {
- mFixedRenderAhead = false;
- mRenderAheadDepth = 0;
- } else {
- mFixedRenderAhead = true;
- mRenderAheadDepth = static_cast<uint32_t>(renderAhead);
- }
-}
-
SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
// can't rely on prior content of window if viewport size changes
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index cc4eb32..b31883b 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -193,9 +193,6 @@
return mUseForceDark;
}
- // Must be called before setSurface
- void setRenderAheadDepth(int renderAhead);
-
SkISize getNextFrameSize() const;
private:
@@ -211,7 +208,6 @@
bool isSwapChainStuffed();
bool surfaceRequiresRedraw();
- void setPresentTime();
void setupPipelineSurface();
SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
@@ -232,9 +228,6 @@
// painted onto its surface.
bool mIsDirty = false;
SwapBehavior mSwapBehavior = SwapBehavior::kSwap_default;
- bool mFixedRenderAhead = false;
- uint32_t mRenderAheadDepth = 0;
- uint32_t mRenderAheadCapacity = 0;
struct SwapHistory {
SkRect damage;
nsecs_t vsyncTime;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b51f6dc..0ade8dd 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -295,11 +295,6 @@
mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
-void RenderProxy::setRenderAheadDepth(int renderAhead) {
- mRenderThread.queue().post(
- [context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); });
-}
-
int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
SkBitmap* bitmap) {
auto& thread = RenderThread::getInstance();
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 33dabc9..a4adb16 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -123,23 +123,6 @@
void removeFrameMetricsObserver(FrameMetricsObserver* observer);
void setForceDark(bool enable);
- /**
- * Sets a render-ahead depth on the backing renderer. This will increase latency by
- * <swapInterval> * renderAhead and increase memory usage by (3 + renderAhead) * <resolution>.
- * In return the renderer will be less susceptible to jitter, resulting in a smoother animation.
- *
- * Not recommended to use in response to anything touch driven, but for canned animations
- * where latency is not a concern careful use may be beneficial.
- *
- * Note that when increasing this there will be a frame gap of N frames where N is
- * renderAhead - <current renderAhead>. When decreasing this if there are any pending
- * frames they will retain their prior renderAhead value, so it will take a few frames
- * for the decrease to flush through.
- *
- * @param renderAhead How far to render ahead, must be in the range [0..2]
- */
- void setRenderAheadDepth(int renderAhead);
-
static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
int bottom, SkBitmap* bitmap);
static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index 74a039b..91022cf 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -38,7 +38,6 @@
int count = 0;
int reportFrametimeWeight = 0;
bool renderOffscreen = true;
- int renderAhead = 0;
};
template <class T>
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index eda5d22..8c7d261 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -153,11 +153,6 @@
proxy->resetProfileInfo();
proxy->fence();
- if (opts.renderAhead) {
- usleep(33000);
- }
- proxy->setRenderAheadDepth(opts.renderAhead);
-
ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight);
nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC);
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 88d33c3..174a140 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -69,7 +69,6 @@
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
--renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
- --render-ahead=NUM Sets how far to render-ahead. Must be 0 (default), 1, or 2.
)");
}
@@ -171,7 +170,6 @@
Onscreen,
Offscreen,
Renderer,
- RenderAhead,
};
}
@@ -187,7 +185,6 @@
{"onscreen", no_argument, nullptr, LongOpts::Onscreen},
{"offscreen", no_argument, nullptr, LongOpts::Offscreen},
{"renderer", required_argument, nullptr, LongOpts::Renderer},
- {"render-ahead", required_argument, nullptr, LongOpts::RenderAhead},
{0, 0, 0, 0}};
static const char* SHORT_OPTIONS = "c:r:h";
@@ -286,16 +283,6 @@
gOpts.renderOffscreen = true;
break;
- case LongOpts::RenderAhead:
- if (!optarg) {
- error = true;
- }
- gOpts.renderAhead = atoi(optarg);
- if (gOpts.renderAhead < 0 || gOpts.renderAhead > 2) {
- error = true;
- }
- break;
-
case 'h':
printHelp();
exit(EXIT_SUCCESS);
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/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index d40de76..205c1f4 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -354,7 +354,7 @@
/**
* @hide
- * @return the internal device tyoe
+ * @return the internal device type
*/
public int getInternalType() {
return mPort.type();
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/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/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index fd71670..606cd57 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -322,7 +322,8 @@
void onDeviceSelected(String callingPackage, String deviceAddress) {
mServiceCallback.complete(new Association(
- getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false));
+ getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false,
+ System.currentTimeMillis()));
}
void onCancel() {
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/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index a746139..fbe15bb 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -21,6 +21,7 @@
import static android.net.NetworkRequest.Type.LISTEN;
import static android.net.NetworkRequest.Type.REQUEST;
import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
import static android.net.QosCallback.QosCallbackRegistrationException;
import android.annotation.CallbackExecutor;
@@ -3721,7 +3722,8 @@
printStackTrace();
checkCallbackNotNull(callback);
Preconditions.checkArgument(
- reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities");
+ reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null,
+ "null NetworkCapabilities");
final NetworkRequest request;
final String callingPackageName = mContext.getOpPackageName();
try {
@@ -4192,8 +4194,9 @@
}
/**
- * Registers to receive notifications about changes in the system default network. The callbacks
- * will continue to be called until either the application exits or
+ * Registers to receive notifications about changes in the application's default network. This
+ * may be a physical network or a virtual network, such as a VPN that applies to the
+ * application. The callbacks will continue to be called until either the application exits or
* {@link #unregisterNetworkCallback(NetworkCallback)} is called.
*
* <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
@@ -4206,7 +4209,7 @@
* {@link #unregisterNetworkCallback(NetworkCallback)}.
*
* @param networkCallback The {@link NetworkCallback} that the system will call as the
- * system default network changes.
+ * application's default network changes.
* The callback is invoked on the default internal Handler.
* @throws RuntimeException if the app already has too many callbacks registered.
*/
@@ -4216,10 +4219,46 @@
}
/**
+ * Registers to receive notifications about changes in the application's default network. This
+ * may be a physical network or a virtual network, such as a VPN that applies to the
+ * application. The callbacks will continue to be called until either the application exits or
+ * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+ *
+ * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
+ * number of outstanding requests to 100 per app (identified by their UID), shared with
+ * all variants of this method, of {@link #requestNetwork} as well as
+ * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}.
+ * Requesting a network with this method will count toward this limit. If this limit is
+ * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources,
+ * make sure to unregister the callbacks with
+ * {@link #unregisterNetworkCallback(NetworkCallback)}.
+ *
+ * @param networkCallback The {@link NetworkCallback} that the system will call as the
+ * application's default network changes.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ * @throws RuntimeException if the app already has too many callbacks registered.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+ @NonNull Handler handler) {
+ CallbackHandler cbHandler = new CallbackHandler(handler);
+ sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+ TRACK_DEFAULT, TYPE_NONE, cbHandler);
+ }
+
+ /**
* Registers to receive notifications about changes in the system default network. The callbacks
* will continue to be called until either the application exits or
* {@link #unregisterNetworkCallback(NetworkCallback)} is called.
*
+ * This method should not be used to determine networking state seen by applications, because in
+ * many cases, most or even all application traffic may not use the default network directly,
+ * and traffic from different applications may go on different networks by default. As an
+ * example, if a VPN is connected, traffic from all applications might be sent through the VPN
+ * and not onto the system default network. Applications or system components desiring to do
+ * determine network state as seen by applications should use other methods such as
+ * {@link #registerDefaultNetworkCallback(NetworkCallback, Handler)}.
+ *
* <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
* number of outstanding requests to 100 per app (identified by their UID), shared with
* all variants of this method, of {@link #requestNetwork} as well as
@@ -4233,20 +4272,19 @@
* system default network changes.
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
* @throws RuntimeException if the app already has too many callbacks registered.
+ *
+ * @hide
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+ @SystemApi(client = MODULE_LIBRARIES)
+ @SuppressLint({"ExecutorRegistration", "PairedRegistration"})
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
@NonNull Handler handler) {
- // This works because if the NetworkCapabilities are null,
- // ConnectivityService takes them from the default request.
- //
- // Since the capabilities are exactly the same as the default request's
- // capabilities, this request is guaranteed, at all times, to be
- // satisfied by the same network, if any, that satisfies the default
- // request, i.e., the system default network.
CallbackHandler cbHandler = new CallbackHandler(handler);
sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
- TRACK_DEFAULT, TYPE_NONE, cbHandler);
+ TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler);
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 55b2c3c..9d67f0b 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -762,12 +762,14 @@
final int originalSignalStrength = mSignalStrength;
final int originalOwnerUid = getOwnerUid();
final int[] originalAdministratorUids = getAdministratorUids();
+ final TransportInfo originalTransportInfo = getTransportInfo();
clearAll();
mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS)
| (1 << TRANSPORT_TEST);
mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
mNetworkSpecifier = originalSpecifier;
mSignalStrength = originalSignalStrength;
+ mTransportInfo = originalTransportInfo;
// Only retain the owner and administrator UIDs if they match the app registering the remote
// caller that registered the network.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 6540397..b4a651c 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -104,17 +104,14 @@
* callbacks about the single, highest scoring current network
* (if any) that matches the specified NetworkCapabilities, or
*
- * - TRACK_DEFAULT, a hybrid of the two designed such that the
- * framework will issue callbacks for the single, highest scoring
- * current network (if any) that matches the capabilities of the
- * default Internet request (mDefaultRequest), but which cannot cause
- * the framework to either create or retain the existence of any
- * specific network. Note that from the point of view of the request
- * matching code, TRACK_DEFAULT is identical to REQUEST: its special
- * behaviour is not due to different semantics, but to the fact that
- * the system will only ever create a TRACK_DEFAULT with capabilities
- * that are identical to the default request's capabilities, thus
- * causing it to share fate in every way with the default request.
+ * - TRACK_DEFAULT, which causes the framework to issue callbacks for
+ * the single, highest scoring current network (if any) that will
+ * be chosen for an app, but which cannot cause the framework to
+ * either create or retain the existence of any specific network.
+ *
+ * - TRACK_SYSTEM_DEFAULT, which causes the framework to send callbacks
+ * for the network (if any) that satisfies the default Internet
+ * request.
*
* - BACKGROUND_REQUEST, like REQUEST but does not cause any networks
* to retain the NET_CAPABILITY_FOREGROUND capability. A network with
@@ -137,6 +134,7 @@
TRACK_DEFAULT,
REQUEST,
BACKGROUND_REQUEST,
+ TRACK_SYSTEM_DEFAULT,
};
/**
@@ -601,6 +599,8 @@
return NetworkRequestProto.TYPE_REQUEST;
case BACKGROUND_REQUEST:
return NetworkRequestProto.TYPE_BACKGROUND_REQUEST;
+ case TRACK_SYSTEM_DEFAULT:
+ return NetworkRequestProto.TYPE_TRACK_SYSTEM_DEFAULT;
default:
return NetworkRequestProto.TYPE_UNKNOWN;
}
diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/packages/Connectivity/framework/src/android/net/VpnManager.java
index 1812509..1e30283 100644
--- a/packages/Connectivity/framework/src/android/net/VpnManager.java
+++ b/packages/Connectivity/framework/src/android/net/VpnManager.java
@@ -55,13 +55,29 @@
public class VpnManager {
/** Type representing a lack of VPN @hide */
public static final int TYPE_VPN_NONE = -1;
- /** VPN service type code @hide */
+
+ /**
+ * A VPN created by an app using the {@link VpnService} API.
+ * @hide
+ */
public static final int TYPE_VPN_SERVICE = 1;
- /** Platform VPN type code @hide */
+
+ /**
+ * A VPN created using a {@link VpnManager} API such as {@link #startProvisionedVpnProfile}.
+ * @hide
+ */
public static final int TYPE_VPN_PLATFORM = 2;
+ /**
+ * An IPsec VPN created by the built-in LegacyVpnRunner.
+ * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead.
+ * @hide
+ */
+ @Deprecated
+ public static final int TYPE_VPN_LEGACY = 3;
+
/** @hide */
- @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM})
+ @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY})
@Retention(RetentionPolicy.SOURCE)
public @interface VpnType {}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 84dacfd..d10ff40 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -251,5 +251,5 @@
<integer name="def_accessibility_magnification_capabilities">3</integer>
<!-- Default for Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW -->
- <bool name="def_enable_non_resizable_multi_window">false</bool>
+ <bool name="def_enable_non_resizable_multi_window">true</bool>
</resources>
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/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 35a2195..f3ec39d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -74,9 +74,7 @@
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:textSize="100dp"
- android:includeFontPadding="false"
android:fontFamily="@font/clock"
- android:lineSpacingMultiplier=".7"
android:typeface="monospace"
android:elegantTextHeight="false"
dozeWeight="200"
@@ -96,8 +94,6 @@
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:textSize="@dimen/large_clock_text_size"
- android:includeFontPadding="false"
- android:lineSpacingMultiplier=".7"
android:fontFamily="@font/clock"
android:typeface="monospace"
android:elegantTextHeight="false"
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 827cf4a..109442d 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -16,8 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#242424" /> <!-- 14% of white -->
- <padding android:paddingTop="@dimen/ongoing_appops_chip_bg_padding"
- android:paddingBottom="@dimen/ongoing_appops_chip_bg_padding" />
+ <solid android:color="#FFFFFF" />
<corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
</shape>
\ No newline at end of file
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/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 3c30632..bad5826 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -22,19 +22,14 @@
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
- android:focusable="true" >
+ android:focusable="true"
+ android:minWidth="48dp" >
- <FrameLayout
- android:id="@+id/background"
+ <LinearLayout
+ android:id="@+id/icons_container"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_width="wrap_content"
- android:minWidth="48dp"
- android:layout_gravity="center_vertical">
- <LinearLayout
- android:id="@+id/icons_container"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="center_vertical"
- />
- </FrameLayout>
+ android:gravity="center_vertical"
+ android:layout_gravity="center"
+ />
</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
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/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5bb46a8..08cd655 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -163,10 +163,14 @@
<!-- heads up elevation that is added if the view is pinned -->
<dimen name="heads_up_pinned_elevation">16dp</dimen>
- <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+ <!-- Height of a messaging notifications with actions at least. Note that this is an upper bound
and the notification won't use this much, but is measured with wrap_content -->
<dimen name="notification_messaging_actions_min_height">196dp</dimen>
+ <!-- Height of a call notification. Note that this is an upper bound
+ and the notification won't use this much, but is measured with wrap_content -->
+ <dimen name="call_notification_full_height">172dp</dimen>
+
<!-- a threshold in dp per second that is considered fast scrolling -->
<dimen name="scroll_fast_threshold">1500dp</dimen>
@@ -688,6 +692,11 @@
<!-- The amount to shift the clocks during a small/large transition -->
<dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
+ <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+ <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+
<item name="scrim_behind_alpha" format="float" type="dimen">0.62</item>
<!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
@@ -1170,17 +1179,13 @@
<dimen name="display_cutout_margin_consumption">0px</dimen>
<!-- Height of the Ongoing App Ops chip -->
- <dimen name="ongoing_appops_chip_height">32dp</dimen>
- <!-- Padding between background of Ongoing App Ops chip and content -->
- <dimen name="ongoing_appops_chip_bg_padding">8dp</dimen>
+ <dimen name="ongoing_appops_chip_height">24dp</dimen>
<!-- Side padding between background of Ongoing App Ops chip and content -->
<dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
- <!-- Margin between icons of Ongoing App Ops chip when QQS-->
- <dimen name="ongoing_appops_chip_icon_margin_collapsed">0dp</dimen>
- <!-- Margin between icons of Ongoing App Ops chip when QS-->
- <dimen name="ongoing_appops_chip_icon_margin_expanded">2dp</dimen>
+ <!-- Margin between icons of Ongoing App Ops chip -->
+ <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
<!-- Icon size of Ongoing App Ops chip -->
- <dimen name="ongoing_appops_chip_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
+ <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
<dimen name="ongoing_appops_chip_bg_corner_radius">16dp</dimen>
@@ -1349,4 +1354,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..6196e4a 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -39,4 +39,6 @@
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
+
+ <bool name="flag_toast_style">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c72548..afdf23b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -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/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/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index e38cf23..5c943f6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -245,9 +245,11 @@
void setSideStageVisibility(in boolean visible) = 36;
/** Removes the split-screen stages. */
void exitSplitScreen() = 37;
- void startTask(in int taskId, in int stage, in int position, in Bundle options) = 38;
+ /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 38;
+ void startTask(in int taskId, in int stage, in int position, in Bundle options) = 39;
void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
- in Bundle options, in UserHandle user) = 39;
+ in Bundle options, in UserHandle user) = 40;
void startIntent(
- in PendingIntent intent, in int stage, in int position, in Bundle options) = 40;
+ in PendingIntent intent, in int stage, in int position, in Bundle options) = 41;
}
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/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 59e81cf..0a117c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -16,37 +16,72 @@
package com.android.keyguard;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.Color;
import android.graphics.Paint;
+import android.icu.text.NumberFormat;
import android.util.MathUtils;
import com.android.settingslib.Utils;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.util.ViewController;
+import java.util.Locale;
+import java.util.Objects;
import java.util.TimeZone;
/**
- * Controls the color of a GradientTextClock.
+ * Controller for an AnimatableClockView.
*/
public class AnimatableClockController extends ViewController<AnimatableClockView> {
+ private static final int FORMAT_NUMBER = 1234567890;
private final StatusBarStateController mStatusBarStateController;
- private final int[] mDozingColors = new int[] {Color.WHITE, Color.WHITE};
- private int[] mLockScreenColors = new int[2];
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final int mDozingColor = Color.WHITE;
+ private int mLockScreenColor;
private boolean mIsDozing;
+ private Locale mLocale;
+
+ private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
+ private final String mBurmeseNumerals;
+ private final float mBurmeseLineSpacing;
+ private final float mDefaultLineSpacing;
public AnimatableClockController(
AnimatableClockView view,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ BroadcastDispatcher broadcastDispatcher) {
super(view);
mStatusBarStateController = statusBarStateController;
mIsDozing = mStatusBarStateController.isDozing();
+ mBroadcastDispatcher = broadcastDispatcher;
+
+ mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
+ mBurmeseLineSpacing = getContext().getResources().getFloat(
+ R.dimen.keyguard_clock_line_spacing_scale_burmese);
+ mDefaultLineSpacing = getContext().getResources().getFloat(
+ R.dimen.keyguard_clock_line_spacing_scale);
}
+ private BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateLocale();
+ }
+ };
+
@Override
protected void onViewAttached() {
+ updateLocale();
+ mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
mStatusBarStateController.addCallback(mStatusBarStateListener);
mIsDozing = mStatusBarStateController.isDozing();
refreshTime();
@@ -55,6 +90,7 @@
@Override
protected void onViewDetached() {
+ mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
@@ -84,11 +120,23 @@
mView.refreshFormat();
}
+ private void updateLocale() {
+ Locale currLocale = Locale.getDefault();
+ if (!Objects.equals(currLocale, mLocale)) {
+ mLocale = currLocale;
+ NumberFormat nf = NumberFormat.getInstance(mLocale);
+ if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
+ mView.setLineSpacingScale(mBurmeseLineSpacing);
+ } else {
+ mView.setLineSpacingScale(mDefaultLineSpacing);
+ }
+ }
+ }
+
private void initColors() {
- mLockScreenColors[0] = Utils.getColorAttrDefaultColor(getContext(),
+ mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
com.android.systemui.R.attr.wallpaperTextColor);
- mLockScreenColors[1] = mLockScreenColors[0]; // same color
- mView.setColors(mDozingColors, mLockScreenColors);
+ mView.setColors(mDozingColor, mLockScreenColor);
mView.animateDoze(mIsDozing, false);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
index ca99563..64b3d73 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -44,12 +44,13 @@
private final Calendar mTime = Calendar.getInstance();
- private CharSequence mFormat;
- private CharSequence mDescFormat;
- private int[] mDozingColors;
- private int[] mLockScreenColors;
private final int mDozingWeight;
private final int mLockScreenWeight;
+ private CharSequence mFormat;
+ private CharSequence mDescFormat;
+ private int mDozingColor;
+ private int mLockScreenColor;
+ private float mLineSpacingScale = 1f;
private TextAnimator mTextAnimator = null;
private Runnable mOnTextAnimatorInitialized;
@@ -111,8 +112,7 @@
() -> {
invalidate();
return Unit.INSTANCE;
- },
- 2 /* number of lines (each can have a unique Paint) */);
+ });
if (mOnTextAnimatorInitialized != null) {
mOnTextAnimatorInitialized.run();
mOnTextAnimatorInitialized = null;
@@ -127,15 +127,20 @@
mTextAnimator.draw(canvas);
}
- void setColors(int[] dozingColors, int[] lockScreenColors) {
- mDozingColors = dozingColors;
- mLockScreenColors = lockScreenColors;
+ void setLineSpacingScale(float scale) {
+ mLineSpacingScale = scale;
+ setLineSpacing(0, mLineSpacingScale);
+ }
+
+ void setColors(int dozingColor, int lockScreenColor) {
+ mDozingColor = dozingColor;
+ mLockScreenColor = lockScreenColor;
}
void animateDoze(boolean isDozing, boolean animate) {
setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */,
-1,
- isDozing ? mDozingColors : mLockScreenColors,
+ isDozing ? mDozingColor : mLockScreenColor,
animate);
}
@@ -152,15 +157,15 @@
private void setTextStyle(
@IntRange(from = 0, to = 1000) int weight,
@FloatRange(from = 0) float textSize,
- int[] colors,
+ int color,
boolean animate) {
if (mTextAnimator != null) {
- mTextAnimator.setTextStyle(weight, textSize, colors, animate, ANIM_DURATION, null);
+ mTextAnimator.setTextStyle(weight, textSize, color, animate, ANIM_DURATION, null);
} else {
// when the text animator is set, update its start values
mOnTextAnimatorInitialized =
() -> mTextAnimator.setTextStyle(
- weight, textSize, colors, false, ANIM_DURATION, null);
+ weight, textSize, color, false, ANIM_DURATION, null);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index e0de180..e375877 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -28,6 +28,7 @@
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ClockPlugin;
@@ -56,6 +57,7 @@
private final ClockManager mClockManager;
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
+ private final BroadcastDispatcher mBroadcastDispatcher;
/**
* Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
@@ -101,7 +103,8 @@
SysuiColorExtractor colorExtractor, ClockManager clockManager,
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
- ContentResolver contentResolver) {
+ ContentResolver contentResolver,
+ BroadcastDispatcher broadcastDispatcher) {
super(keyguardClockSwitch);
mResources = resources;
mStatusBarStateController = statusBarStateController;
@@ -109,6 +112,7 @@
mClockManager = clockManager;
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
+ mBroadcastDispatcher = broadcastDispatcher;
mTimeFormat = Settings.System.getString(contentResolver, Settings.System.TIME_12_24);
}
@@ -231,12 +235,14 @@
mNewLockScreenClockViewController =
new AnimatableClockController(
mView.findViewById(R.id.animatable_clock_view),
- mStatusBarStateController);
+ mStatusBarStateController,
+ mBroadcastDispatcher);
mNewLockScreenClockViewController.init();
mNewLockScreenLargeClockViewController =
new AnimatableClockController(
mView.findViewById(R.id.animatable_clock_view_large),
- mStatusBarStateController);
+ mStatusBarStateController,
+ mBroadcastDispatcher);
mNewLockScreenLargeClockViewController.init();
}
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
index f2d36d1..5735a4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
@@ -54,11 +54,10 @@
*/
class TextAnimator(
layout: Layout,
- private val invalidateCallback: () -> Unit,
- private val numLines: Int = 1
+ private val invalidateCallback: () -> Unit
) {
// Following two members are for mutable for testing purposes.
- internal var textInterpolator: TextInterpolator = TextInterpolator(layout, numLines)
+ internal var textInterpolator: TextInterpolator = TextInterpolator(layout)
internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
addUpdateListener {
@@ -99,7 +98,7 @@
fun setTextStyle(
weight: Int = -1,
textSize: Float = -1f,
- colors: IntArray? = null,
+ color: Int? = null,
animate: Boolean = true,
duration: Long = -1L,
interpolator: TimeInterpolator? = null
@@ -110,21 +109,13 @@
}
if (textSize >= 0) {
- for (targetPaint in textInterpolator.targetPaint)
- targetPaint.textSize = textSize
+ textInterpolator.targetPaint.textSize = textSize
}
if (weight >= 0) {
- for (targetPaint in textInterpolator.targetPaint)
- targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+ textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
}
- if (colors != null) {
- require(colors.size == textInterpolator.targetPaint.size) {
- "colors size (${colors.size}) must be the same size as" +
- " targetPaints size (${textInterpolator.targetPaint.size})," +
- " which was initialized as numLines ($numLines)"
- }
- for ((index, targetPaint) in textInterpolator.targetPaint.withIndex())
- targetPaint.color = colors[index]
+ if (color != null) {
+ textInterpolator.targetPaint.color = color
}
textInterpolator.onTargetPaintModified()
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
index 0d41a2f..5d5797c 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
@@ -30,8 +30,7 @@
* Provide text style linear interpolation for plain text.
*/
class TextInterpolator(
- layout: Layout,
- lines: Int = 1
+ layout: Layout
) {
/**
@@ -40,11 +39,9 @@
* Once you modified the style parameters, you have to call reshapeText to recalculate base text
* layout.
*
- * @return an array list of paint objects representing one paint per line of text. If this
- * list has a smaller size than the number of lines, all extra lines will use the Paint an
- * index 0.
+ * @return a paint object
*/
- val basePaint = createDefaultPaint(layout.paint, lines)
+ val basePaint = TextPaint(layout.paint)
/**
* Returns target paint used for interpolation.
@@ -52,18 +49,9 @@
* Once you modified the style parameters, you have to call reshapeText to recalculate target
* text layout.
*
- * @return an array list of paint objects representing one paint per line of text. If this
- * list has a smaller size than the number of lines, all extra lines will use the Paint an
- * index 0.
+ * @return a paint object
*/
- val targetPaint = createDefaultPaint(layout.paint, lines)
-
- private fun createDefaultPaint(paint: TextPaint, lines: Int): ArrayList<TextPaint> {
- val paintList = ArrayList<TextPaint>()
- for (i in 0 until lines)
- paintList.add(TextPaint(paint))
- return paintList
- }
+ val targetPaint = TextPaint(layout.paint)
/**
* A class represents a single font run.
@@ -102,7 +90,7 @@
private val fontInterpolator = FontInterpolator()
// Recycling object for glyph drawing. Will be extended for the longest font run if needed.
- private val tmpDrawPaints = ArrayList<TextPaint>()
+ private val tmpDrawPaint = TextPaint()
private var tmpPositionArray = FloatArray(20)
/**
@@ -216,10 +204,10 @@
if (progress == 0f) {
return
} else if (progress == 1f) {
- updatePaint(basePaint, targetPaint)
+ basePaint.set(targetPaint)
} else {
- lerp(basePaint, targetPaint, progress, tmpDrawPaints)
- updatePaint(basePaint, tmpDrawPaints)
+ lerp(basePaint, targetPaint, progress, tmpDrawPaint)
+ basePaint.set(tmpDrawPaint)
}
lines.forEach { line ->
@@ -237,21 +225,13 @@
progress = 0f
}
- companion object {
- fun updatePaint(toUpdate: ArrayList<TextPaint>, newValues: ArrayList<TextPaint>) {
- toUpdate.clear()
- for (paint in newValues)
- toUpdate.add(TextPaint(paint))
- }
- }
-
/**
* Draws interpolated text at the given progress.
*
* @param canvas a canvas.
*/
fun draw(canvas: Canvas) {
- lerp(basePaint, targetPaint, progress, tmpDrawPaints)
+ lerp(basePaint, targetPaint, progress, tmpDrawPaint)
lines.forEachIndexed { lineNo, line ->
line.runs.forEach { run ->
canvas.save()
@@ -261,10 +241,7 @@
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
run.fontRuns.forEach { fontRun ->
- if (lineNo >= tmpDrawPaints.size)
- drawFontRun(canvas, run, fontRun, tmpDrawPaints[0])
- else
- drawFontRun(canvas, run, fontRun, tmpDrawPaints[lineNo])
+ drawFontRun(canvas, run, fontRun, tmpDrawPaint)
}
} finally {
canvas.restore()
@@ -430,27 +407,19 @@
}
// Linear interpolate the paint.
- private fun lerp(
- from: ArrayList<TextPaint>,
- to: ArrayList<TextPaint>,
- progress: Float,
- out: ArrayList<TextPaint>
- ) {
- out.clear()
+ private fun lerp(from: Paint, to: Paint, progress: Float, out: Paint) {
+ out.set(from)
+
// Currently only font size & colors are interpolated.
// TODO(172943390): Add other interpolation or support custom interpolator.
- for (index in from.indices) {
- val paint = TextPaint(from[index])
- paint.textSize = MathUtils.lerp(from[index].textSize, to[index].textSize, progress)
- paint.color = ColorUtils.blendARGB(from[index].color, to[index].color, progress)
- out.add(paint)
- }
+ out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress)
+ out.color = ColorUtils.blendARGB(from.color, to.color, progress)
}
// Shape the text and stores the result to out argument.
private fun shapeText(
layout: Layout,
- paints: ArrayList<TextPaint>
+ paint: TextPaint
): List<List<PositionedGlyphs>> {
val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
@@ -458,7 +427,7 @@
val count = layout.getLineEnd(lineNo) - lineStart
val runs = mutableListOf<PositionedGlyphs>()
TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
- paints[lineNo]) { _, _, glyphs, _ ->
+ paint) { _, _, glyphs, _ ->
runs.add(glyphs)
}
out.add(runs)
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/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index 429f67f..d06568a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -24,6 +24,9 @@
*/
interface ControlActionCoordinator {
+ // Handle actions launched from GlobalActionsDialog or ControlDialog
+ var startedFromGlobalActions: Boolean
+
/**
* Close any dialogs which may have been open
*/
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 7cd7e18..247f25e 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.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsComponent
@@ -37,6 +38,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.TaskViewFactory
+import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -48,13 +50,17 @@
private val activityStarter: ActivityStarter,
private val keyguardStateController: KeyguardStateController,
private val globalActionsComponent: GlobalActionsComponent,
- private val taskViewFactory: Optional<TaskViewFactory>
+ private val taskViewFactory: Optional<TaskViewFactory>,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val lazyUiController: Lazy<ControlsUiController>
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
private var pendingAction: Action? = null
private var actionsInProgress = mutableSetOf<String>()
+ override var startedFromGlobalActions: Boolean = true
+
companion object {
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
}
@@ -131,8 +137,8 @@
private fun bouncerOrRun(action: Action) {
if (keyguardStateController.isShowing()) {
- var closeGlobalActions = !keyguardStateController.isUnlocked()
- if (closeGlobalActions) {
+ var closeDialog = !keyguardStateController.isUnlocked()
+ if (closeDialog) {
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
// pending actions will only run after the control state has been refreshed
@@ -141,8 +147,12 @@
activityStarter.dismissKeyguardThenExecute({
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
- if (closeGlobalActions) {
- globalActionsComponent.handleShowGlobalActionsMenu()
+ if (closeDialog) {
+ if (startedFromGlobalActions) {
+ globalActionsComponent.handleShowGlobalActionsMenu()
+ } else {
+ ControlsDialog(context, broadcastDispatcher).show(lazyUiController.get())
+ }
} else {
action.invoke()
}
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 8e878cf..f533cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -66,7 +66,7 @@
val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls)
vg.alpha = 0f
- controller.show(vg, { /* do nothing */ })
+ controller.show(vg, { /* do nothing */ }, false /* startedFromGlobalActions */)
vg.animate()
.alpha(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 4e4c82c..9448877 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -29,7 +29,7 @@
public const val EXTRA_ANIMATE = "extra_animate"
}
- fun show(parent: ViewGroup, dismissGlobalActions: Runnable)
+ fun show(parent: ViewGroup, onDismiss: Runnable, startedFromGlobalActions: Boolean)
fun hide()
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 2b529f9..762362c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -102,7 +102,7 @@
private lateinit var lastItems: List<SelectionItem>
private var popup: ListPopupWindow? = null
private var hidden = true
- private lateinit var dismissGlobalActions: Runnable
+ private lateinit var onDismiss: Runnable
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
@@ -145,13 +145,19 @@
}
}
- override fun show(parent: ViewGroup, dismissGlobalActions: Runnable) {
+ override fun show(
+ parent: ViewGroup,
+ onDismiss: Runnable,
+ startedFromGlobalActions: Boolean
+ ) {
Log.d(ControlsUiController.TAG, "show()")
this.parent = parent
- this.dismissGlobalActions = dismissGlobalActions
+ this.onDismiss = onDismiss
hidden = false
retainCache = false
+ controlActionCoordinator.startedFromGlobalActions = startedFromGlobalActions
+
allStructures = controlsController.get().getFavorites()
selectedStructure = loadPreference(allStructures)
@@ -187,7 +193,7 @@
controlViewsById.clear()
controlsById.clear()
- show(parent, dismissGlobalActions)
+ show(parent, onDismiss, controlActionCoordinator.startedFromGlobalActions)
val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
showAnim.setInterpolator(DecelerateInterpolator(1.0f))
showAnim.setDuration(FADE_IN_MILLIS)
@@ -260,7 +266,7 @@
private fun startActivity(context: Context, intent: Intent) {
// Force animations when transitioning from a dialog to an activity
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
- dismissGlobalActions.run()
+ onDismiss.run()
activityStarter.dismissKeyguardThenExecute({
shadeController.collapsePanel(false)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index ec4a91c..2b362b9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,6 +20,7 @@
import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -92,4 +93,10 @@
@IntoMap
@ClassKey(TvNotificationPanelActivity.class)
public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity);
+
+ /** Inject into PeopleSpaceActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(PeopleSpaceActivity.class)
+ public abstract Activity bindPeopleSpaceActivity(PeopleSpaceActivity activity);
}
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/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ad4c447..8af45a5 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -2229,7 +2229,8 @@
private void showControls(ControlsUiController controller) {
mControlsUiController = controller;
- mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
+ true /* startedFromGlobalActions */);
}
private boolean isWalletViewAvailable() {
@@ -2457,7 +2458,8 @@
return WindowInsets.CONSUMED;
});
if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
+ true /* startedFromGlobalActions */);
}
mBackgroundDrawable.setAlpha(0);
@@ -2632,7 +2634,8 @@
initializeLayout();
mGlobalActionsLayout.updateList();
if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
+ true /* startedFromGlobalActions */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index d0070d8d..8c04143 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -289,7 +289,8 @@
if (hasIndications()) {
pw.println(" All messages:");
for (int type : mIndicationMessages.keySet()) {
- pw.println(" type=" + type + " message=" + mIndicationMessages.get(type));
+ pw.println(" type=" + type
+ + " message=" + mIndicationMessages.get(type).getMessage());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5a918d4..c55fdf4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -67,6 +67,7 @@
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.RemoteAnimationTarget;
@@ -306,6 +307,13 @@
*/
private final SparseIntArray mLastSimStates = new SparseIntArray();
+ /**
+ * Indicates if a SIM card had the SIM PIN enabled during the initialization, before
+ * reaching the SIM_STATE_READY state. The flag is reset to false at SIM_STATE_READY.
+ * Index is the slotId - in case of multiple SIM cards.
+ */
+ private final SparseBooleanArray mSimWasLocked = new SparseBooleanArray();
+
private boolean mDeviceInteractive;
private boolean mGoingToSleep;
@@ -477,10 +485,10 @@
}
}
- boolean simWasLocked;
+ boolean lastSimStateWasLocked;
synchronized (KeyguardViewMediator.this) {
int lastState = mLastSimStates.get(slotId);
- simWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED
+ lastSimStateWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED
|| lastState == TelephonyManager.SIM_STATE_PUK_REQUIRED);
mLastSimStates.append(slotId, simState);
}
@@ -504,17 +512,19 @@
if (simState == TelephonyManager.SIM_STATE_ABSENT) {
// MVNO SIMs can become transiently NOT_READY when switching networks,
// so we should only lock when they are ABSENT.
- if (simWasLocked) {
+ if (lastSimStateWasLocked) {
if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
+ "previous state was locked. Reset the state.");
resetStateLocked();
}
+ mSimWasLocked.append(slotId, false);
}
}
break;
case TelephonyManager.SIM_STATE_PIN_REQUIRED:
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
synchronized (KeyguardViewMediator.this) {
+ mSimWasLocked.append(slotId, true);
if (!mShowing) {
if (DEBUG_SIM_STATES) Log.d(TAG,
"INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
@@ -541,9 +551,10 @@
case TelephonyManager.SIM_STATE_READY:
synchronized (KeyguardViewMediator.this) {
if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing);
- if (mShowing && simWasLocked) {
+ if (mShowing && mSimWasLocked.get(slotId, false)) {
if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the "
- + "previous state was locked. Reset the state.");
+ + "previously was locked. Reset the state.");
+ mSimWasLocked.append(slotId, false);
resetStateLocked();
}
}
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/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 580cbcf..c67aef6 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -37,9 +37,12 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Shows the user their tiles for their priority People (go/live-status).
*/
@@ -54,10 +57,17 @@
private LauncherApps mLauncherApps;
private Context mContext;
private AppWidgetManager mAppWidgetManager;
+ private NotificationEntryManager mNotificationEntryManager;
private int mAppWidgetId;
private boolean mShowSingleConversation;
private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+ @Inject
+ public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager) {
+ super();
+ mNotificationEntryManager = notificationEntryManager;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -91,8 +101,8 @@
*/
private void setTileViewsWithPriorityConversations() {
try {
- List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(
- mContext, mNotificationManager, mPeopleManager, mLauncherApps);
+ List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager,
+ mPeopleManager, mLauncherApps, mNotificationEntryManager);
for (PeopleSpaceTile tile : tiles) {
PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext, mPeopleSpaceLayout,
tile.getId());
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 994dc6d..dd05484 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -60,9 +60,12 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ArrayUtils;
import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.text.SimpleDateFormat;
import java.time.Duration;
@@ -137,7 +140,7 @@
/** Returns a list of map entries corresponding to user's conversations. */
public static List<PeopleSpaceTile> getTiles(
Context context, INotificationManager notificationManager, IPeopleManager peopleManager,
- LauncherApps launcherApps)
+ LauncherApps launcherApps, NotificationEntryManager notificationEntryManager)
throws Exception {
boolean showOnlyPriority = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 1;
@@ -173,6 +176,8 @@
getSortedTiles(peopleManager, launcherApps, mergedStream);
tiles.addAll(recentTiles);
}
+
+ tiles = augmentTilesFromVisibleNotifications(tiles, notificationEntryManager);
return tiles;
}
@@ -258,7 +263,8 @@
ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
IPeopleManager.Stub.asInterface(
ServiceManager.getService(Context.PEOPLE_SERVICE)),
- context.getSystemService(LauncherApps.class));
+ context.getSystemService(LauncherApps.class),
+ Dependency.get(NotificationEntryManager.class));
Optional<PeopleSpaceTile> entry = tiles.stream().filter(
e -> e.getId().equals(shortcutId)).findFirst();
if (entry.isPresent()) {
@@ -339,6 +345,41 @@
&& storedUserId == userId;
}
+ static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(List<PeopleSpaceTile> tiles,
+ NotificationEntryManager notificationEntryManager) {
+ if (notificationEntryManager == null) {
+ Log.w(TAG, "NotificationEntryManager is null");
+ return tiles;
+ }
+ Map<String, NotificationEntry> visibleNotifications = notificationEntryManager
+ .getVisibleNotifications()
+ .stream()
+ .filter(entry -> entry.getRanking() != null
+ && entry.getRanking().getConversationShortcutInfo() != null)
+ .collect(Collectors.toMap(PeopleSpaceUtils::getKey, e -> e));
+ if (DEBUG) {
+ Log.d(TAG, "Number of visible notifications:" + visibleNotifications.size());
+ }
+ return tiles
+ .stream()
+ .map(entry -> augmentTileFromVisibleNotifications(entry, visibleNotifications))
+ .collect(Collectors.toList());
+ }
+
+ static PeopleSpaceTile augmentTileFromVisibleNotifications(PeopleSpaceTile tile,
+ Map<String, NotificationEntry> visibleNotifications) {
+ String shortcutId = tile.getId();
+ String packageName = tile.getPackageName();
+ int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+ String key = getKey(shortcutId, packageName, userId);
+ if (!visibleNotifications.containsKey(key)) {
+ if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key);
+ return tile;
+ }
+ if (DEBUG) Log.d(TAG, "Augmenting tile from visible notifications, key:" + key);
+ return augmentTileFromNotification(tile, visibleNotifications.get(key).getSbn());
+ }
+
/**
* If incoming notification changed tile, store the changes in the tile options.
*/
@@ -355,17 +396,7 @@
}
if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) {
if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId);
- Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn);
- if (message == null) {
- if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping.");
- return;
- }
- storedTile = storedTile
- .toBuilder()
- .setNotificationKey(sbn.getKey())
- .setNotificationContent(message.getText())
- .setNotificationDataUri(message.getDataUri())
- .build();
+ storedTile = augmentTileFromNotification(storedTile, sbn);
} else {
if (DEBUG) {
Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId);
@@ -380,6 +411,21 @@
updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile);
}
+ static PeopleSpaceTile augmentTileFromNotification(PeopleSpaceTile tile,
+ StatusBarNotification sbn) {
+ Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn);
+ if (message == null) {
+ if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping.");
+ return tile;
+ }
+ return tile
+ .toBuilder()
+ .setNotificationKey(sbn.getKey())
+ .setNotificationContent(message.getText())
+ .setNotificationDataUri(message.getDataUri())
+ .build();
+ }
+
private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId,
PeopleSpaceTile tile) {
if (tile == null) {
@@ -792,6 +838,16 @@
return lookupKeysWithBirthdaysToday;
}
+ static String getKey(NotificationEntry entry) {
+ if (entry.getRanking() == null || entry.getRanking().getConversationShortcutInfo() == null
+ || entry.getSbn() == null || entry.getSbn().getUser() == null) {
+ return null;
+ }
+ return getKey(entry.getRanking().getConversationShortcutInfo().getId(),
+ entry.getSbn().getPackageName(),
+ entry.getSbn().getUser().getIdentifier());
+ }
+
/**
* Returns the uniquely identifying key for the conversation.
*
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index bee54e4..bee9889 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -135,12 +135,14 @@
try {
String sbnShortcutId = sbn.getShortcutId();
if (sbnShortcutId == null) {
+ if (DEBUG) Log.d(TAG, "Sbn shortcut id is null");
return;
}
int[] widgetIds = mAppWidgetService.getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
);
if (widgetIds.length == 0) {
+ Log.d(TAG, "No app widget ids returned");
return;
}
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -148,6 +150,7 @@
String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId);
Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
if (storedWidgetIds.isEmpty()) {
+ Log.d(TAG, "No stored widget ids");
return;
}
for (String widgetIdString : storedWidgetIds) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
index fb33aff..80794cb 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
@@ -28,9 +28,11 @@
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.people.PeopleSpaceTileView;
import com.android.systemui.people.PeopleSpaceUtils;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import java.util.ArrayList;
import java.util.List;
@@ -42,6 +44,7 @@
private IPeopleManager mPeopleManager;
private INotificationManager mNotificationManager;
+ private NotificationEntryManager mNotificationEntryManager;
private PackageManager mPackageManager;
private LauncherApps mLauncherApps;
private List<PeopleSpaceTile> mTiles = new ArrayList<>();
@@ -56,6 +59,7 @@
if (DEBUG) Log.d(TAG, "onCreate called");
mNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
mPackageManager = mContext.getPackageManager();
mPeopleManager = IPeopleManager.Stub.asInterface(
ServiceManager.getService(Context.PEOPLE_SERVICE));
@@ -70,7 +74,7 @@
private void setTileViewsWithPriorityConversations() {
try {
mTiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager,
- mPeopleManager, mLauncherApps);
+ mPeopleManager, mLauncherApps, mNotificationEntryManager);
} catch (Exception e) {
Log.e(TAG, "Couldn't retrieve conversations", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 870e714..bdd37fc 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -16,11 +16,11 @@
import android.content.Context
import android.util.AttributeSet
-import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import com.android.settingslib.Utils
import com.android.systemui.R
class OngoingPrivacyChip @JvmOverloads constructor(
@@ -30,26 +30,13 @@
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
- private val iconMarginExpanded = context.resources.getDimensionPixelSize(
- R.dimen.ongoing_appops_chip_icon_margin_expanded)
- private val iconMarginCollapsed = context.resources.getDimensionPixelSize(
- R.dimen.ongoing_appops_chip_icon_margin_collapsed)
- private val iconSize =
- context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
- private val iconColor = context.resources.getColor(
- R.color.status_bar_clock_color, context.theme)
- private val sidePadding =
- context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
- private val backgroundDrawable = context.getDrawable(R.drawable.privacy_chip_bg)
+ private var iconMargin = 0
+ private var iconSize = 0
+ private var iconColor = 0
+ private var defaultBackgroundColor = 0
+ private var cameraBackgroundColor = 0
+
private lateinit var iconsContainer: LinearLayout
- private lateinit var back: FrameLayout
- var expanded = false
- set(value) {
- if (value != field) {
- field = value
- updateView(PrivacyChipBuilder(context, privacyList))
- }
- }
var privacyList = emptyList<PrivacyItem>()
set(value) {
@@ -60,15 +47,13 @@
override fun onFinishInflate() {
super.onFinishInflate()
- back = requireViewById(R.id.background)
iconsContainer = requireViewById(R.id.icons_container)
+
+ updateResources()
}
// Should only be called if the builder icons or app changed
private fun updateView(builder: PrivacyChipBuilder) {
- back.background = if (expanded) backgroundDrawable else null
- val padding = if (expanded) sidePadding else 0
- back.setPaddingRelative(padding, 0, padding, 0)
fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) {
iconsContainer.removeAllViews()
chipBuilder.generateIcons().forEachIndexed { i, it ->
@@ -81,7 +66,7 @@
iconsContainer.addView(image, iconSize, iconSize)
if (i != 0) {
val lp = image.layoutParams as MarginLayoutParams
- lp.marginStart = if (expanded) iconMarginExpanded else iconMarginCollapsed
+ lp.marginStart = iconMargin
image.layoutParams = lp
}
}
@@ -90,10 +75,11 @@
if (!privacyList.isEmpty()) {
generateContentDescription(builder)
setIcons(builder, iconsContainer)
- val lp = iconsContainer.layoutParams as FrameLayout.LayoutParams
- lp.gravity = Gravity.CENTER_VERTICAL or
- (if (expanded) Gravity.CENTER_HORIZONTAL else Gravity.END)
- iconsContainer.layoutParams = lp
+ if (builder.types.contains(PrivacyType.TYPE_CAMERA)) {
+ iconsContainer.background.setTint(cameraBackgroundColor)
+ } else {
+ iconsContainer.background.setTint(defaultBackgroundColor)
+ }
} else {
iconsContainer.removeAllViews()
}
@@ -105,4 +91,20 @@
contentDescription = context.getString(
R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
}
+
+ private fun updateResources() {
+ iconMargin = context.resources
+ .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
+ iconSize = context.resources
+ .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
+ iconColor =
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+ defaultBackgroundColor = context.getColor(R.color.privacy_circle_microphone_location)
+ cameraBackgroundColor = context.getColor(R.color.privacy_circle_camera)
+
+ val padding = context.resources
+ .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+ iconsContainer.setPaddingRelative(padding, 0, padding, 0)
+ iconsContainer.background = context.getDrawable(R.drawable.privacy_chip_bg)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index d248ab5..c8edaec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -362,7 +362,7 @@
if(view == parent || view == null) return;
// Ignore tile pages as they can have some offset we don't want to take into account in
// RTL.
- if (!(view instanceof PagedTileLayout.TilePage)) {
+ if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) {
loc1[0] += view.getLeft();
loc1[1] += view.getTop();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 4248cf2..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,8 +73,6 @@
protected QuickQSPanel mHeaderQsPanel;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
- private TouchAnimator mPrivacyChipAlphaAnimator;
- private DualToneHandler mDualToneHandler;
private View mSystemIconsView;
private View mQuickQsStatusIcons;
@@ -111,8 +107,6 @@
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
- mDualToneHandler = new DualToneHandler(
- new ContextThemeWrapper(context, R.style.QSHeaderTheme));
}
@Override
@@ -150,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);
@@ -272,19 +264,16 @@
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();
updateHeaderTextContainerAlphaAnimator();
- updatePrivacyChipAlphaAnimator();
}
private void updateStatusIconAlphaAnimator() {
@@ -299,12 +288,6 @@
.build();
}
- private void updatePrivacyChipAlphaAnimator() {
- mPrivacyChipAlphaAnimator = new TouchAnimator.Builder()
- .addFloat(mPrivacyChip, "alpha", 1, 0, 1)
- .build();
- }
-
/** */
public void setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController) {
if (mExpanded == expanded) return;
@@ -344,10 +327,6 @@
mHeaderTextContainerView.setVisibility(INVISIBLE);
}
}
- if (mPrivacyChipAlphaAnimator != null) {
- mPrivacyChip.setExpanded(expansionFraction > 0.5);
- mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
- }
mKeyguardExpansionFraction = keyguardExpansionFraction;
}
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..eddcf8c 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,9 +38,6 @@
private TextView mCarrierText;
private ImageView mMobileSignal;
private ImageView mMobileRoaming;
- private DualToneHandler mDualToneHandler;
- private ColorStateList mColorForegroundStateList;
- private float mColorForegroundIntensity;
private CellSignalState mLastSignalState;
public QSCarrier(Context context) {
@@ -64,7 +59,6 @@
@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);
@@ -74,11 +68,6 @@
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);
}
/**
@@ -92,8 +81,8 @@
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);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 01a8c1c..5b2a7e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -620,6 +620,19 @@
}
@Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ if (!verifyCaller("exitSplitScreenOnHide")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(s -> s.exitSplitScreenOnHide(exitSplitScreenOnHide));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void startTask(int taskId, int stage, int position, Bundle options) {
if (!verifyCaller("startTask")) {
return;
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/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..778f813b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -74,4 +74,8 @@
public boolean isPeopleTileEnabled() {
return mFlagReader.isEnabled(R.bool.flag_conversations);
}
+
+ public boolean isToastStyleEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_toast_style);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a03fc13..845d321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -71,6 +71,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.CachingIconView;
+import com.android.internal.widget.CallLayout;
import com.android.internal.widget.MessagingLayout;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
@@ -165,6 +166,7 @@
private int mMaxSmallHeightLarge;
private int mMaxSmallHeightMedia;
private int mMaxExpandedHeight;
+ private int mMaxCallHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
@@ -645,8 +647,9 @@
}
private void updateLimitsForView(NotificationContentView layout) {
- boolean customView = layout.getContractedChild() != null
- && layout.getContractedChild().getId()
+ View contractedView = layout.getContractedChild();
+ boolean customView = contractedView != null
+ && contractedView.getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
@@ -661,7 +664,8 @@
View expandedView = layout.getExpandedChild();
boolean isMediaLayout = expandedView != null
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
- boolean isMessagingLayout = layout.getContractedChild() instanceof MessagingLayout;
+ boolean isMessagingLayout = contractedView instanceof MessagingLayout;
+ boolean isCallLayout = contractedView instanceof CallLayout;
boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (customView && beforeS && !mIsSummaryWithChildren) {
@@ -684,6 +688,8 @@
// make sure we don't crop them terribly. We actually need to revisit this and give
// them a headerless design, then remove this hack.
smallHeight = mMaxSmallHeightLarge;
+ } else if (isCallLayout) {
+ smallHeight = mMaxCallHeight;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
smallHeight = mMaxSmallHeightLarge;
} else {
@@ -1645,6 +1651,8 @@
R.dimen.notification_min_height_media);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
+ mMaxCallHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.call_notification_full_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
new file mode 100644
index 0000000..4541ebf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.notification.row.wrapper
+
+import android.content.Context
+import android.view.View
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.CallLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/**
+ * Wraps a notification containing a call template
+ */
+class NotificationCallTemplateViewWrapper constructor(
+ ctx: Context,
+ view: View,
+ row: ExpandableNotificationRow
+) : NotificationTemplateViewWrapper(ctx, view, row) {
+
+ private val minHeightWithActions: Int =
+ NotificationUtils.getFontScaledHeight(ctx, R.dimen.call_notification_full_height)
+ private val callLayout: CallLayout = view as CallLayout
+
+ private lateinit var conversationIconView: CachingIconView
+ private lateinit var conversationBadgeBg: View
+ private lateinit var expandBtn: View
+ private lateinit var appName: View
+ private lateinit var conversationTitleView: View
+
+ private fun resolveViews() {
+ with(callLayout) {
+ conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
+ conversationBadgeBg =
+ requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
+ expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+ appName = requireViewById(com.android.internal.R.id.app_name_text)
+ conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+ }
+ }
+
+ override fun onContentUpdated(row: ExpandableNotificationRow) {
+ // Reinspect the notification. Before the super call, because the super call also updates
+ // the transformation types and we need to have our values set by then.
+ resolveViews()
+ super.onContentUpdated(row)
+ }
+
+ override fun updateTransformedTypes() {
+ // This also clears the existing types
+ super.updateTransformedTypes()
+ addTransformedViews(
+ appName,
+ conversationTitleView
+ )
+ addViewsTransformingToSimilar(
+ conversationIconView,
+ conversationBadgeBg,
+ expandBtn
+ )
+ }
+
+ override fun disallowSingleClick(x: Float, y: Float): Boolean {
+ val isOnExpandButton = expandBtn.visibility == View.VISIBLE &&
+ isOnView(expandBtn, x, y)
+ return isOnExpandButton || super.disallowSingleClick(x, y)
+ }
+
+ override fun getMinLayoutHeight(): Int = minHeightWithActions
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index c49f6cb..905bccf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.view.View
-import android.view.View.GONE
import android.view.ViewGroup
import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
@@ -48,8 +47,8 @@
private lateinit var conversationIconView: CachingIconView
private lateinit var conversationBadgeBg: View
- private lateinit var expandButton: View
- private lateinit var expandButtonContainer: View
+ private lateinit var expandBtn: View
+ private lateinit var expandBtnContainer: View
private lateinit var imageMessageContainer: ViewGroup
private lateinit var messagingLinearLayout: MessagingLinearLayout
private lateinit var conversationTitleView: View
@@ -66,9 +65,8 @@
conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
conversationBadgeBg =
requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
- expandButton = requireViewById(com.android.internal.R.id.expand_button)
- expandButtonContainer =
- requireViewById(com.android.internal.R.id.expand_button_container)
+ expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+ expandBtnContainer = requireViewById(com.android.internal.R.id.expand_button_container)
importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
appName = requireViewById(com.android.internal.R.id.app_name_text)
conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
@@ -126,7 +124,7 @@
addViewsTransformingToSimilar(
conversationIconView,
conversationBadgeBg,
- expandButton,
+ expandBtn,
importanceRing,
facePileTop,
facePileBottom,
@@ -134,11 +132,9 @@
)
}
- override fun getExpandButton() = super.getExpandButton()
-
override fun setShelfIconVisible(visible: Boolean) {
if (conversationLayout.isImportantConversation) {
- if (conversationIconView.visibility != GONE) {
+ if (conversationIconView.visibility != View.GONE) {
conversationIconView.isForceHidden = visible
// We don't want the small icon to be hidden by the extended wrapper, as force
// hiding the conversationIcon will already do that via its listener.
@@ -152,7 +148,7 @@
override fun getShelfTransformationTarget(): View? =
if (conversationLayout.isImportantConversation)
- if (conversationIconView.visibility != GONE)
+ if (conversationIconView.visibility != View.GONE)
conversationIconView
else
// A notification with a fallback icon was set to important. Currently
@@ -169,8 +165,8 @@
conversationLayout.updateExpandability(expandable, onClickListener)
override fun disallowSingleClick(x: Float, y: Float): Boolean {
- val isOnExpandButton = expandButtonContainer.visibility == View.VISIBLE &&
- isOnView(expandButtonContainer, x, y)
+ val isOnExpandButton = expandBtnContainer.visibility == View.VISIBLE &&
+ isOnView(expandBtnContainer, x, y)
return isOnExpandButton || super.disallowSingleClick(x, y)
}
@@ -179,10 +175,4 @@
minHeightWithActions
else
super.getMinLayoutHeight()
-
- private fun addTransformedViews(vararg vs: View?) =
- vs.forEach { view -> view?.let(mTransformationHelper::addTransformedView) }
-
- private fun addViewsTransformingToSimilar(vararg vs: View?) =
- vs.forEach { view -> view?.let(mTransformationHelper::addViewTransformingToSimilar) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 97201f5..34bc537 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -36,7 +36,6 @@
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationExpandButton;
-import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
@@ -60,6 +59,7 @@
private CachingIconView mIcon;
private NotificationExpandButton mExpandButton;
private View mAltExpandTarget;
+ private View mIconContainer;
protected NotificationHeaderView mNotificationHeader;
protected NotificationTopLineView mNotificationTopLine;
private TextView mHeaderText;
@@ -112,6 +112,7 @@
mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
+ mIconContainer = mView.findViewById(com.android.internal.R.id.conversation_icon_container);
mLeftIcon = mView.findViewById(com.android.internal.R.id.left_icon);
mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
@@ -203,11 +204,8 @@
public void clearConversationSkin() {
if (mAppNameText != null) {
final ColorStateList colors = mAppNameText.getTextColors();
- final int textAppearance = Utils.getThemeAttr(
- mAppNameText.getContext(),
- com.android.internal.R.attr.notificationHeaderTextAppearance,
+ mAppNameText.setTextAppearance(
com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
- mAppNameText.setTextAppearance(textAppearance);
mAppNameText.setTextColor(colors);
MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
final int marginStart = mAppNameText.getResources().getDimensionPixelSize(
@@ -265,19 +263,11 @@
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_EXPANDER,
mExpandButton);
- if (mWorkProfileImage != null) {
- mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage);
- }
if (mIsLowPriority && mHeaderText != null) {
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
mHeaderText);
}
- if (mAudiblyAlertedIcon != null) {
- mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
- }
- if (mFeedbackIcon != null) {
- mTransformationHelper.addViewTransformingToSimilar(mFeedbackIcon);
- }
+ addViewsTransformingToSimilar(mWorkProfileImage, mAudiblyAlertedIcon, mFeedbackIcon);
}
@Override
@@ -287,6 +277,9 @@
if (mAltExpandTarget != null) {
mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
}
+ if (mIconContainer != null) {
+ mIconContainer.setOnClickListener(expandable ? onClickListener : null);
+ }
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
@@ -371,4 +364,20 @@
super.setVisible(visible);
mTransformationHelper.setVisible(visible);
}
+
+ protected void addTransformedViews(View... views) {
+ for (View view : views) {
+ if (view != null) {
+ mTransformationHelper.addTransformedView(view);
+ }
+ }
+ }
+
+ protected void addViewsTransformingToSimilar(View... views) {
+ for (View view : views) {
+ if (view != null) {
+ mTransformationHelper.addViewTransformingToSimilar(view);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index b3d1a94..5fff8c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -74,6 +74,8 @@
return new NotificationMessagingTemplateViewWrapper(ctx, v, row);
} else if ("conversation".equals(v.getTag())) {
return new NotificationConversationTemplateViewWrapper(ctx, v, row);
+ } else if ("call".equals(v.getTag())) {
+ return new NotificationCallTemplateViewWrapper(ctx, v, row);
}
Class<? extends Notification.Style> style =
row.getEntry().getSbn().getNotification().getNotificationStyle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4f4a504..738cab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -31,12 +31,11 @@
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
-import android.net.IConnectivityManager;
import android.net.Network;
import android.net.NetworkRequest;
+import android.net.VpnManager;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.KeyChain;
@@ -84,7 +83,7 @@
private final Context mContext;
private final ConnectivityManager mConnectivityManager;
- private final IConnectivityManager mConnectivityManagerService;
+ private final VpnManager mVpnManager;
private final DevicePolicyManager mDevicePolicyManager;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
@@ -116,8 +115,7 @@
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
- ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ mVpnManager = context.getSystemService(VpnManager.class);
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mBgExecutor = bgExecutor;
@@ -399,25 +397,19 @@
private void updateState() {
// Find all users with an active VPN
SparseArray<VpnConfig> vpns = new SparseArray<>();
- try {
- for (UserInfo user : mUserManager.getUsers()) {
- VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id);
- if (cfg == null) {
+ for (UserInfo user : mUserManager.getUsers()) {
+ VpnConfig cfg = mVpnManager.getVpnConfig(user.id);
+ if (cfg == null) {
+ continue;
+ } else if (cfg.legacy) {
+ // Legacy VPNs should do nothing if the network is disconnected. Third-party
+ // VPN warnings need to continue as traffic can still go to the app.
+ LegacyVpnInfo legacyVpn = mVpnManager.getLegacyVpnInfo(user.id);
+ if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
continue;
- } else if (cfg.legacy) {
- // Legacy VPNs should do nothing if the network is disconnected. Third-party
- // VPN warnings need to continue as traffic can still go to the app.
- LegacyVpnInfo legacyVpn = mConnectivityManagerService.getLegacyVpnInfo(user.id);
- if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
- continue;
- }
}
- vpns.put(user.id, cfg);
}
- } catch (RemoteException rme) {
- // Roll back to previous state
- Log.e(TAG, "Unable to list active VPNs", rme);
- return;
+ vpns.put(user.id, cfg);
}
mCurrentVpns = vpns;
}
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 0552396..68d74ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -113,7 +113,9 @@
private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
private boolean mResumeUserOnGuestLogout = true;
private boolean mSimpleUserSwitcher;
- private boolean mAddUsersWhenLocked;
+ // When false, there won't be any visual affordance to add a new user from the keyguard even if
+ // the user is unlocked
+ private boolean mAddUsersFromLockScreen;
private boolean mPauseRefreshUsers;
private int mSecondaryUser = UserHandle.USER_NULL;
private Intent mSecondaryUserServiceIntent;
@@ -204,7 +206,7 @@
}
mForcePictureLoadForUserId.clear();
- final boolean addUsersWhenLocked = mAddUsersWhenLocked;
+ final boolean addUsersWhenLocked = mAddUsersFromLockScreen;
new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
@SuppressWarnings("unchecked")
@Override
@@ -554,7 +556,7 @@
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
public void onChange(boolean selfChange) {
mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
- mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
+ mAddUsersFromLockScreen = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
refreshUsers(UserHandle.USER_NULL);
};
@@ -610,43 +612,26 @@
}
public int getUserCount() {
- boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
- && mKeyguardStateController.isMethodSecure()
- && !mKeyguardStateController.canDismissLockScreen();
- if (!secureKeyguardShowing) {
- return getUsers().size();
- }
- // The lock screen is secure and showing. Filter out restricted records.
- final int userSize = getUsers().size();
- int count = 0;
- for (int i = 0; i < userSize; i++) {
- if (getUsers().get(i).isGuest) continue;
- if (getUsers().get(i).isRestricted) {
- break;
- } else {
- count++;
- }
- }
- return count;
+ return countUsers(false);
}
@Override
public int getCount() {
- boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
- && mKeyguardStateController.isMethodSecure()
- && !mKeyguardStateController.canDismissLockScreen();
- if (!secureKeyguardShowing) {
- return getUsers().size();
- }
- // The lock screen is secure and showing. Filter out restricted records.
+ return countUsers(true);
+ }
+
+ private int countUsers(boolean includeGuest) {
+ boolean keyguardShowing = mKeyguardStateController.isShowing();
final int userSize = getUsers().size();
int count = 0;
for (int i = 0; i < userSize; i++) {
- if (getUsers().get(i).isRestricted) {
- break;
- } else {
- count++;
+ if (getUsers().get(i).isGuest && !includeGuest) {
+ continue;
}
+ if (getUsers().get(i).isRestricted && keyguardShowing) {
+ break;
+ }
+ count++;
}
return count;
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index e357577..7863914 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.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -229,49 +232,53 @@
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();
// 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 =
+ new OverlayIdentifier(categoryToPackage.get(category));
+ setEnabled(transaction, overlayInfo, category, userHandles, true);
}
}
+ for (Pair<String, String> packageToDisable : overlaysToDisable) {
+ OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
+ setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false);
+ }
+
+ mExecutor.execute(() -> {
+ mOverlayManager.commit(transaction.build());
+ });
}
- private void setEnabled(
- String packageName, String category, Set<UserHandle> handles, boolean enabled) {
+ @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) {
- setEnabledAsync(packageName, userHandle, enabled);
+ transaction.setEnabled(identifier, enabled, userHandle.getIdentifier());
}
if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
- setEnabledAsync(packageName, UserHandle.SYSTEM, enabled);
+ transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier());
}
}
- 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);
- }
- } catch (SecurityException | IllegalStateException e) {
- Log.e(TAG,
- String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e);
- }
- });
- }
-
/**
* @inherit
*/
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/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/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a2eaea1..70a7b7a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -34,6 +34,7 @@
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -75,6 +76,8 @@
NotificationIconAreaController mNotificationIconAreaController;
@Mock
ContentResolver mContentResolver;
+ @Mock
+ BroadcastDispatcher mBroadcastDispatcher;
private KeyguardClockSwitchController mController;
@@ -94,7 +97,8 @@
mClockManager,
mKeyguardSliceViewController,
mNotificationIconAreaController,
- mContentResolver);
+ mContentResolver,
+ mBroadcastDispatcher);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
index 53d84db..7b4f14d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
@@ -34,9 +34,9 @@
import kotlin.math.ceil
-private val PAINT = arrayListOf(TextPaint().apply {
+private val PAINT = TextPaint().apply {
textSize = 32f
-})
+}
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -49,10 +49,10 @@
@Test
fun testAnimationStarted() {
- val layout = makeLayout("Hello, World", PAINT[0])
+ val layout = makeLayout("Hello, World", PAINT)
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
- val paint = arrayListOf(mock(TextPaint::class.java))
+ val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator = TextAnimator(layout, {}).apply {
@@ -81,10 +81,10 @@
@Test
fun testAnimationNotStarted() {
- val layout = makeLayout("Hello, World", PAINT[0])
+ val layout = makeLayout("Hello, World", PAINT)
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
- val paint = arrayListOf(mock(TextPaint::class.java))
+ val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator = TextAnimator(layout, {}).apply {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
index 1206dab..149e179 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
@@ -21,9 +21,9 @@
import android.testing.AndroidTestingRunner
import android.text.Layout
import android.text.StaticLayout
-import android.text.TextPaint
import android.text.TextDirectionHeuristic
import android.text.TextDirectionHeuristics
+import android.text.TextPaint
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
@@ -40,13 +40,13 @@
textSize = 32f
}
-private val START_PAINT = arrayListOf(TextPaint(PAINT).apply {
+private val START_PAINT = TextPaint(PAINT).apply {
fontVariationSettings = "'wght' 400"
-})
+}
-private val END_PAINT = arrayListOf(TextPaint(PAINT).apply {
+private val END_PAINT = TextPaint(PAINT).apply {
fontVariationSettings = "'wght' 700"
-})
+}
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -67,16 +67,16 @@
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+ interp.basePaint.set(START_PAINT)
interp.onBasePaintModified()
- TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+ interp.targetPaint.set(END_PAINT)
interp.onTargetPaintModified()
// Just after created TextInterpolator, it should have 0 progress.
assertThat(interp.progress).isEqualTo(0f)
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- val expected = makeLayout(TEXT, START_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(TEXT, START_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
assertThat(expected.sameAs(actual)).isTrue()
}
@@ -86,15 +86,15 @@
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+ interp.basePaint.set(START_PAINT)
interp.onBasePaintModified()
- TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+ interp.targetPaint.set(END_PAINT)
interp.onTargetPaintModified()
interp.progress = 1f
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- val expected = makeLayout(TEXT, END_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(TEXT, END_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
assertThat(expected.sameAs(actual)).isTrue()
}
@@ -104,10 +104,10 @@
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+ interp.basePaint.set(START_PAINT)
interp.onBasePaintModified()
- TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+ interp.targetPaint.set(END_PAINT)
interp.onTargetPaintModified()
// We cannot expect exact text layout of the middle position since we don't use text shaping
@@ -115,9 +115,9 @@
// end state.
interp.progress = 0.5f
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0])
+ assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT)
.toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
- assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0])
+ assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT)
.toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
}
@@ -126,10 +126,10 @@
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+ interp.basePaint.set(START_PAINT)
interp.onBasePaintModified()
- TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+ interp.targetPaint.set(END_PAINT)
interp.onTargetPaintModified()
interp.progress = 0.5f
@@ -148,16 +148,16 @@
val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR)
val interp = TextInterpolator(layout)
- TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+ interp.basePaint.set(START_PAINT)
interp.onBasePaintModified()
- TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+ interp.targetPaint.set(END_PAINT)
interp.onTargetPaintModified()
// Just after created TextInterpolator, it should have 0 progress.
assertThat(interp.progress).isEqualTo(0f)
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.LTR)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.LTR)
.toBitmap(BMP_WIDTH, BMP_HEIGHT)
assertThat(expected.sameAs(actual)).isTrue()
@@ -168,16 +168,16 @@
val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
val interp = TextInterpolator(layout)
- TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+ interp.basePaint.set(START_PAINT)
interp.onBasePaintModified()
- TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+ interp.targetPaint.set(END_PAINT)
interp.onTargetPaintModified()
// Just after created TextInterpolator, it should have 0 progress.
assertThat(interp.progress).isEqualTo(0f)
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.RTL)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
.toBitmap(BMP_WIDTH, BMP_HEIGHT)
assertThat(expected.sameAs(actual)).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 6db21f9..4ee2759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.people;
import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
@@ -52,6 +53,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
@@ -64,6 +66,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -82,10 +87,17 @@
private static final int WIDGET_ID_WITH_SHORTCUT = 1;
private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2;
- private static final String SHORTCUT_ID = "101";
+ private static final String SHORTCUT_ID_1 = "101";
+ private static final String SHORTCUT_ID_2 = "202";
+ private static final String SHORTCUT_ID_3 = "303";
+ private static final String SHORTCUT_ID_4 = "404";
private static final String NOTIFICATION_KEY = "notification_key";
private static final String NOTIFICATION_CONTENT = "notification_content";
private static final String TEST_LOOKUP_KEY = "lookup_key";
+ private static final String NOTIFICATION_TEXT_1 = "notification_text_1";
+ private static final String NOTIFICATION_TEXT_2 = "notification_text_2";
+ private static final String NOTIFICATION_TEXT_3 = "notification_text_3";
+ private static final String NOTIFICATION_TEXT_4 = "notification_text_4";
private static final int TEST_COLUMN_INDEX = 1;
private static final Uri URI = Uri.parse("fake_uri");
private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
@@ -97,20 +109,66 @@
.build();
private static final PeopleSpaceTile PERSON_TILE =
new PeopleSpaceTile
- .Builder(SHORTCUT_ID, "username", ICON, new Intent())
+ .Builder(SHORTCUT_ID_1, "username", ICON, new Intent())
.setNotificationKey(NOTIFICATION_KEY)
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
.build();
private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
- SHORTCUT_ID).setLongLabel(
+ SHORTCUT_ID_1).setLongLabel(
"name").setPerson(PERSON)
.build();
private final ShortcutInfo mShortcutInfoWithoutPerson = new ShortcutInfo.Builder(mContext,
- SHORTCUT_ID).setLongLabel(
+ SHORTCUT_ID_1).setLongLabel(
"name")
.build();
+ private final Notification mNotification1 = new Notification.Builder(mContext, "test")
+ .setContentTitle("TEST_TITLE")
+ .setContentText("TEST_TEXT")
+ .setShortcutId(SHORTCUT_ID_1)
+ .setStyle(new Notification.MessagingStyle(PERSON)
+ .addMessage(new Notification.MessagingStyle.Message(
+ NOTIFICATION_TEXT_1, 0, PERSON))
+ .addMessage(new Notification.MessagingStyle.Message(
+ NOTIFICATION_TEXT_2, 20, PERSON))
+ .addMessage(new Notification.MessagingStyle.Message(
+ NOTIFICATION_TEXT_3, 10, PERSON))
+ )
+ .build();
+ private final Notification mNotification2 = new Notification.Builder(mContext, "test2")
+ .setContentTitle("TEST_TITLE")
+ .setContentText("OTHER_TEXT")
+ .setShortcutId(SHORTCUT_ID_2)
+ .setStyle(new Notification.MessagingStyle(PERSON)
+ .addMessage(new Notification.MessagingStyle.Message(
+ NOTIFICATION_TEXT_4, 0, PERSON))
+ )
+ .build();
+ private final Notification mNotification3 = new Notification.Builder(mContext, "test2")
+ .setContentTitle("TEST_TITLE")
+ .setContentText("OTHER_TEXT")
+ .setShortcutId(SHORTCUT_ID_3)
+ .setStyle(new Notification.MessagingStyle(PERSON))
+ .build();
+ private final NotificationEntry mNotificationEntry1 = new NotificationEntryBuilder()
+ .setNotification(mNotification1)
+ .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_1).build())
+ .setUser(UserHandle.of(0))
+ .setPkg(PACKAGE_NAME)
+ .build();
+ private final NotificationEntry mNotificationEntry2 = new NotificationEntryBuilder()
+ .setNotification(mNotification2)
+ .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_2).build())
+ .setUser(UserHandle.of(0))
+ .setPkg(PACKAGE_NAME)
+ .build();
+ private final NotificationEntry mNotificationEntry3 = new NotificationEntryBuilder()
+ .setNotification(mNotification3)
+ .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_3).build())
+ .setUser(UserHandle.of(0))
+ .setPkg(PACKAGE_NAME)
+ .build();
@Mock
private NotificationListener mListenerService;
@@ -130,6 +188,8 @@
private ContentResolver mMockContentResolver;
@Mock
private Context mMockContext;
+ @Mock
+ private NotificationEntryManager mNotificationEntryManager;
@Before
public void setUp() throws RemoteException {
@@ -152,15 +212,17 @@
isNull())).thenReturn(mMockCursor);
when(mMockContext.getString(R.string.birthday_status)).thenReturn(
mContext.getString(R.string.birthday_status));
+ when(mNotificationEntryManager.getVisibleNotifications())
+ .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
}
@Test
public void testGetTilesReturnsSortedListWithMultipleRecentConversations() throws Exception {
// Ensure the less-recent Important conversation is before more recent conversations.
ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
- SHORTCUT_ID, false, 3);
+ SHORTCUT_ID_1, false, 3);
ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
- SHORTCUT_ID + 1,
+ SHORTCUT_ID_1 + 1,
true, 1);
when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
new ParceledListSlice(Arrays.asList(
@@ -169,9 +231,9 @@
// Ensure the non-Important conversation is sorted between these recent conversations.
ConversationChannel recentConversationBeforeNonImportantConversation =
getConversationChannel(
- SHORTCUT_ID + 2, 4);
+ SHORTCUT_ID_1 + 2, 4);
ConversationChannel recentConversationAfterNonImportantConversation =
- getConversationChannel(SHORTCUT_ID + 3,
+ getConversationChannel(SHORTCUT_ID_1 + 3,
2);
when(mPeopleManager.getRecentConversations()).thenReturn(
new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
@@ -179,7 +241,8 @@
List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles(
mContext, mNotificationManager, mPeopleManager,
- mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList());
+ mLauncherApps, mNotificationEntryManager)
+ .stream().map(tile -> tile.getId()).collect(Collectors.toList());
assertThat(orderedShortcutIds).containsExactly(
// Even though the oldest conversation, should be first since "important"
@@ -196,11 +259,11 @@
throws Exception {
// Ensure the less-recent Important conversation is before more recent conversations.
ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
- SHORTCUT_ID, false, 3);
+ SHORTCUT_ID_1, false, 3);
ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
- SHORTCUT_ID + 1, true, 3);
+ SHORTCUT_ID_1 + 1, true, 3);
ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
- SHORTCUT_ID + 2,
+ SHORTCUT_ID_1 + 2,
true, 1);
when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
new ParceledListSlice(Arrays.asList(
@@ -210,9 +273,9 @@
// Ensure the non-Important conversation is sorted between these recent conversations.
ConversationChannel recentConversationBeforeNonImportantConversation =
getConversationChannel(
- SHORTCUT_ID + 3, 4);
+ SHORTCUT_ID_1 + 3, 4);
ConversationChannel recentConversationAfterNonImportantConversation =
- getConversationChannel(SHORTCUT_ID + 4,
+ getConversationChannel(SHORTCUT_ID_1 + 4,
2);
when(mPeopleManager.getRecentConversations()).thenReturn(
new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
@@ -220,7 +283,8 @@
List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles(
mContext, mNotificationManager, mPeopleManager,
- mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList());
+ mLauncherApps, mNotificationEntryManager)
+ .stream().map(tile -> tile.getId()).collect(Collectors.toList());
assertThat(orderedShortcutIds).containsExactly(
// Important conversations should be sorted at the beginning.
@@ -238,7 +302,7 @@
Notification notification = new Notification.Builder(mContext, "test")
.setContentTitle("TEST_TITLE")
.setContentText("TEST_TEXT")
- .setShortcutId(SHORTCUT_ID)
+ .setShortcutId(SHORTCUT_ID_1)
.build();
StatusBarNotification sbn = new SbnBuilder()
.setNotification(notification)
@@ -341,24 +405,126 @@
@Test
public void testGetLastMessagingStyleMessage() {
- Notification notification = new Notification.Builder(mContext, "test")
- .setContentTitle("TEST_TITLE")
- .setContentText("TEST_TEXT")
- .setShortcutId(SHORTCUT_ID)
- .setStyle(new Notification.MessagingStyle(PERSON)
- .addMessage(new Notification.MessagingStyle.Message("text1", 0, PERSON))
- .addMessage(new Notification.MessagingStyle.Message("text2", 20, PERSON))
- .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON))
- )
- .build();
StatusBarNotification sbn = new SbnBuilder()
- .setNotification(notification)
+ .setNotification(mNotification1)
.build();
Notification.MessagingStyle.Message lastMessage =
PeopleSpaceUtils.getLastMessagingStyleMessage(sbn);
- assertThat(lastMessage.getText()).isEqualTo("text2");
+ assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+ }
+
+ @Test
+ public void testAugmentTileFromNotification() {
+ StatusBarNotification sbn = new SbnBuilder()
+ .setNotification(mNotification1)
+ .build();
+
+ PeopleSpaceTile tile =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUid(0)
+ .build();
+ PeopleSpaceTile actual = PeopleSpaceUtils
+ .augmentTileFromNotification(tile, sbn);
+
+ assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+ }
+
+ @Test
+ public void testAugmentTileFromNotificationNoContent() {
+ StatusBarNotification sbn = new SbnBuilder()
+ .setNotification(mNotification3)
+ .build();
+
+ PeopleSpaceTile tile =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_3, "userName", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUid(0)
+ .build();
+ PeopleSpaceTile actual = PeopleSpaceUtils
+ .augmentTileFromNotification(tile, sbn);
+
+ assertThat(actual.getNotificationKey()).isEqualTo(null);
+ assertThat(actual.getNotificationContent()).isEqualTo(null);
+ }
+
+ @Test
+ public void testAugmentTileFromVisibleNotifications() {
+ PeopleSpaceTile tile =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUid(0)
+ .build();
+ PeopleSpaceTile actual = PeopleSpaceUtils
+ .augmentTileFromVisibleNotifications(tile,
+ Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1));
+
+ assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+ }
+
+ @Test
+ public void testAugmentTileFromVisibleNotificationsDifferentShortcutId() {
+ PeopleSpaceTile tile =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_4, "userName", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUid(0)
+ .build();
+ PeopleSpaceTile actual = PeopleSpaceUtils
+ .augmentTileFromVisibleNotifications(tile,
+ Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1));
+
+ assertThat(actual.getNotificationContent()).isEqualTo(null);
+ }
+
+ @Test
+ public void testAugmentTilesFromVisibleNotificationsSingleTile() {
+ PeopleSpaceTile tile =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUid(0)
+ .build();
+ List<PeopleSpaceTile> actualList = PeopleSpaceUtils
+ .augmentTilesFromVisibleNotifications(List.of(tile), mNotificationEntryManager);
+
+ assertThat(actualList.size()).isEqualTo(1);
+ assertThat(actualList.get(0).getNotificationContent().toString())
+ .isEqualTo(NOTIFICATION_TEXT_2);
+
+ verify(mNotificationEntryManager, times(1)).getVisibleNotifications();
+ }
+
+ @Test
+ public void testAugmentTilesFromVisibleNotificationsMultipleTiles() {
+ PeopleSpaceTile tile1 =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUid(1)
+ .build();
+ PeopleSpaceTile tile2 =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_2, "userName2", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUid(0)
+ .build();
+ List<PeopleSpaceTile> actualList = PeopleSpaceUtils
+ .augmentTilesFromVisibleNotifications(List.of(tile1, tile2),
+ mNotificationEntryManager);
+
+ assertThat(actualList.size()).isEqualTo(2);
+ assertThat(actualList.get(0).getNotificationContent().toString())
+ .isEqualTo(NOTIFICATION_TEXT_2);
+ assertThat(actualList.get(1).getNotificationContent().toString())
+ .isEqualTo(NOTIFICATION_TEXT_4);
+
+ verify(mNotificationEntryManager, times(1)).getVisibleNotifications();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index ef314ad..9470141 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -421,6 +421,7 @@
throws Exception {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+ setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
Notification notificationWithoutMessagingStyle = new Notification.Builder(mContext)
.setContentTitle("TEST_TITLE")
@@ -429,15 +430,21 @@
.build();
StatusBarNotification sbn = new SbnBuilder()
.setNotification(notificationWithoutMessagingStyle)
+ .setPkg(TEST_PACKAGE_A)
+ .setUid(0)
.build();
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setSbn(sbn)
.setId(1));
mClock.advanceTime(MIN_LINGER_DURATION);
- verify(mAppWidgetManager, never())
- .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any());
- verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle options = requireNonNull(mBundleArgumentCaptor.getValue());
+ assertThat((PeopleSpaceTile) options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE))
+ .isEqualTo(PERSON_TILE);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
any());
}
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..6c99efc 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,16 @@
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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+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;
@@ -87,6 +90,8 @@
OverlayManager mOverlayManager;
@Mock
DumpManager mDumpManager;
+ @Mock
+ OverlayManagerTransaction.Builder mTransactionBuilder;
private ThemeOverlayApplier mManager;
@@ -94,7 +99,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,
@@ -148,9 +158,11 @@
@Test
public void allCategoriesSpecified_allEnabledExclusively() {
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+ verify(mOverlayManager).commit(any());
for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+ verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
+ eq(true), eq(TEST_USER.getIdentifier()));
}
}
@@ -160,11 +172,12 @@
for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) {
if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(
- entry.getValue(), UserHandle.SYSTEM);
+ verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(entry.getValue())),
+ eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
} else {
- verify(mOverlayManager, never()).setEnabledExclusiveInCategory(
- entry.getValue(), UserHandle.SYSTEM);
+ verify(mTransactionBuilder, never()).setEnabled(
+ eq(new OverlayIdentifier(entry.getValue())),
+ eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
}
}
}
@@ -177,8 +190,10 @@
mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles);
for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle);
+ verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
+ eq(true), eq(TEST_USER.getIdentifier()));
+ verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
+ eq(true), eq(newUserHandle.getIdentifier()));
}
}
@@ -199,12 +214,15 @@
mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
for (String overlayPackage : categoryToPackage.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+ verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(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
@@ -212,7 +230,9 @@
mManager.applyCurrentUserOverlays(Maps.newArrayMap(), 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()));
}
}
@@ -223,9 +243,12 @@
mManager.applyCurrentUserOverlays(categoryToPackage, 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
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/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/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/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 25f4abe..903a071 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -19,12 +19,12 @@
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
@@ -147,15 +147,15 @@
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this));
mMultiFingerGestures.add(
- new MultiFingerMultiTapAndHold(
- mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, this));
- mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
mMultiFingerGestures.add(
new MultiFingerMultiTapAndHold(
mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this));
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this));
+ mMultiFingerGestures.add(
+ new MultiFingerMultiTapAndHold(
+ mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD, this));
// Three-finger taps.
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 3, 1, GESTURE_3_FINGER_SINGLE_TAP, this));
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 67f654e..6d72ca7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1492,7 +1492,7 @@
}
final Dataset dataset = (Dataset) result;
final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
- if (!isPinnedDataset(oldDataset)) {
+ if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
authenticatedResponse.getDatasets().set(datasetIdx, dataset);
}
autoFill(requestId, datasetIdx, dataset, false);
@@ -1513,6 +1513,21 @@
}
/**
+ * Returns whether the dataset returned from the authentication result is ephemeral or not.
+ * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
+ * information.
+ */
+ private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset,
+ @NonNull Bundle authResultData) {
+ if (authResultData.containsKey(
+ AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
+ return authResultData.getBoolean(
+ AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET);
+ }
+ return isPinnedDataset(oldDataset);
+ }
+
+ /**
* A dataset can potentially have multiple fields, and it's possible that some of the fields'
* has inline presentation and some don't. It's also possible that some of the fields'
* inline presentation is pinned and some isn't. So the concept of whether a dataset is
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 96b69dc..10b00d3 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -161,6 +161,8 @@
private static final boolean DEBUG = false;
private static final String LOG_TAG = "CompanionDeviceManagerService";
+ private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
+
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
@@ -170,6 +172,7 @@
private static final String XML_ATTR_DEVICE = "device";
private static final String XML_ATTR_PROFILE = "profile";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
+ private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_FILE_NAME = "companion_device_manager_associations.xml";
private final CompanionDeviceManagerImpl mImpl;
@@ -606,7 +609,8 @@
association.getDeviceMacAddress(),
association.getPackageName(),
association.getDeviceProfile(),
- active /* notifyOnDeviceNearby */);
+ active, /* notifyOnDeviceNearby */
+ association.getTimeApprovedMs());
} else {
return association;
}
@@ -639,6 +643,15 @@
}
@Override
+ public boolean canPairWithoutPrompt(
+ String packageName, String deviceMacAddress, int userId) {
+ return CollectionUtils.any(
+ getAllAssociations(userId, packageName, deviceMacAddress),
+ a -> System.currentTimeMillis() - a.getTimeApprovedMs()
+ < PAIR_WITHOUT_PROMPT_WINDOW_MS);
+ }
+
+ @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
@@ -877,6 +890,8 @@
Boolean.toString(
association.isNotifyOnDeviceNearby()));
}
+ tag.attribute(null, XML_ATTR_TIME_APPROVED,
+ Long.toString(association.getTimeApprovedMs()));
tag.endTag(null, XML_TAG_ASSOCIATION);
});
@@ -921,7 +936,6 @@
}
}
- @Nullable
private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
return CollectionUtils.filter(
getAllAssociations(userId),
@@ -941,6 +955,14 @@
}
}
+ private Set<Association> getAllAssociations(
+ int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
+ return CollectionUtils.filter(
+ getAllAssociations(userId),
+ a -> Objects.equals(packageFilter, a.getPackageName())
+ && Objects.equals(addressFilter, a.getDeviceMacAddress()));
+ }
+
private Set<Association> readAllAssociations(int userId) {
final AtomicFile file = getStorageFileForUser(userId);
@@ -962,12 +984,14 @@
final String profile = parser.getAttributeValue(null, XML_ATTR_PROFILE);
final boolean persistentGrants = Boolean.valueOf(
parser.getAttributeValue(null, XML_ATTR_NOTIFY_DEVICE_NEARBY));
+ final long timeApproved = parseLongOrDefault(
+ parser.getAttributeValue(null, XML_ATTR_TIME_APPROVED), 0L);
if (appPackage == null || deviceAddress == null) continue;
result = ArrayUtils.add(result,
new Association(userId, deviceAddress, appPackage,
- profile, persistentGrants));
+ profile, persistentGrants, timeApproved));
}
return result;
} catch (XmlPullParserException | IOException e) {
@@ -1293,6 +1317,15 @@
return result;
}
+ private static long parseLongOrDefault(String str, long def) {
+ try {
+ return Long.parseLong(str);
+ } catch (NumberFormatException e) {
+ Log.w(LOG_TAG, "Failed to parse", e);
+ return def;
+ }
+ }
+
private class ShellCmd extends ShellCommand {
public static final String USAGE = "help\n"
+ "list USER_ID\n"
@@ -1321,7 +1354,8 @@
int userId = getNextArgInt();
String pkg = getNextArgRequired();
String address = getNextArgRequired();
- addAssociation(new Association(userId, address, pkg, null, false));
+ addAssociation(new Association(userId, address, pkg, null, false,
+ System.currentTimeMillis()));
}
break;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 96cfe02..25890b0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -82,6 +82,7 @@
":framework_native_aidl",
":gsiservice_aidl",
":idmap2_aidl",
+ ":idmap2_core_aidl",
":inputconstants_aidl",
":installd_aidl",
":storaged_aidl",
@@ -104,7 +105,6 @@
"android.hardware.power-V1-java",
"android.hardware.power-V1.0-java",
"android.hardware.vibrator-V2-java",
- "android.net.ipsec.ike.stubs.module_lib",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"service-permission.stubs.system_server",
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/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 958c15c..e996eb4 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -17,6 +17,7 @@
package android.os;
import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import java.util.Collection;
@@ -37,6 +38,9 @@
*/
public abstract String[] getMobileIfaces();
+ /** Returns CPU times for system server thread groups. */
+ public abstract SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes();
+
/**
* Inform battery stats how many deferred jobs existed when the app got launched and how
* long ago was the last job execution for the app.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2f88351..277152d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -132,12 +132,14 @@
import android.net.RouteInfoParcel;
import android.net.SocketKeepalive;
import android.net.TetheringManager;
+import android.net.TransportInfo;
import android.net.UidRange;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnService;
+import android.net.VpnTransportInfo;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
@@ -5747,6 +5749,7 @@
throw new SecurityException("Insufficient permissions to specify legacy type");
}
}
+ final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities;
final int callingUid = mDeps.getCallingUid();
final NetworkRequest.Type reqType;
try {
@@ -5757,11 +5760,15 @@
switch (reqType) {
case TRACK_DEFAULT:
// If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
- // is unused and will be replaced by the one from the default network request.
- // This allows callers to keep track of the system default network.
+ // is unused and will be replaced by ones appropriate for the caller.
+ // This allows callers to keep track of the default network for their app.
networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid);
enforceAccessPermission();
break;
+ case TRACK_SYSTEM_DEFAULT:
+ enforceSettingsPermission();
+ networkCapabilities = new NetworkCapabilities(defaultNc);
+ break;
case BACKGROUND_REQUEST:
enforceNetworkStackOrSettingsPermission();
// Fall-through since other checks are the same with normal requests.
@@ -5780,6 +5787,7 @@
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
+
// Set the UID range for this request to the single UID of the requester, or to an empty
// set of UIDs if the caller has the appropriate permission and UIDs have not been set.
// This will overwrite any allowed UIDs in the requested capabilities. Though there
@@ -5799,6 +5807,16 @@
new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
if (DBG) log("requestNetwork for " + nri);
+ // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
+ // copied from the default request above. (This is necessary to ensure, for example, that
+ // the callback does not leak sensitive information to unprivileged apps.) Check that the
+ // changes don't alter request matching.
+ if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT &&
+ (!networkCapabilities.equalRequestableCapabilities(defaultNc))) {
+ Log.wtf(TAG, "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+ + networkCapabilities + " vs. " + defaultNc);
+ }
+
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
if (timeoutMs > 0) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
@@ -6389,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();
}
@@ -8462,22 +8478,11 @@
}
}
- /**
- * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
- * for testing.
- */
- private Vpn enforceActiveVpnOrNetworkStackPermission() {
- if (checkNetworkStackPermission()) {
- return null;
- }
- synchronized (mVpns) {
- Vpn vpn = getVpnIfOwner();
- if (vpn != null) {
- return vpn;
- }
- }
- throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
- + "permission");
+ private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+ if (vpn == null) return VpnManager.TYPE_VPN_NONE;
+ final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
+ if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
+ return ((VpnTransportInfo) ti).type;
}
/**
@@ -8487,14 +8492,6 @@
* connection is not found.
*/
public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
- final Vpn vpn = enforceActiveVpnOrNetworkStackPermission();
-
- // Only VpnService based VPNs should be able to get this information.
- if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) {
- throw new SecurityException(
- "getConnectionOwnerUid() not allowed for non-VpnService VPNs");
- }
-
if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
}
@@ -8502,8 +8499,15 @@
final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol,
connectionInfo.local, connectionInfo.remote);
- /* Filter out Uids not associated with the VPN. */
- if (vpn != null && !vpn.appliesToUid(uid)) {
+ if (uid == INVALID_UID) return uid; // Not found.
+
+ // Connection owner UIDs are visible only to the network stack and to the VpnService-based
+ // VPN, if any, that applies to the UID that owns the connection.
+ if (checkNetworkStackPermission()) return uid;
+
+ final NetworkAgentInfo vpn = getVpnForUid(uid);
+ if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
+ || vpn.networkCapabilities.getOwnerUid() != Binder.getCallingUid()) {
return INVALID_UID;
}
diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java
index c56cef2..a83c981 100644
--- a/services/core/java/com/android/server/EntropyMixer.java
+++ b/services/core/java/com/android/server/EntropyMixer.java
@@ -16,12 +16,6 @@
package com.android.server;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -33,10 +27,15 @@
import android.os.SystemProperties;
import android.util.Slog;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
/**
* A service designed to load and periodically save "randomness"
- * for the Linux kernel RNG and to mix in data from Hardware RNG (if present)
- * into the Linux RNG.
+ * for the Linux kernel RNG.
*
* <p>When a Linux system starts up, the entropy pool associated with
* {@code /dev/random} may be in a fairly predictable state. Applications which
@@ -45,15 +44,8 @@
* this effect, it's helpful to carry the entropy pool information across
* shutdowns and startups.
*
- * <p>On systems with Hardware RNG (/dev/hw_random), a block of output from HW
- * RNG is mixed into the Linux RNG on EntropyMixer's startup and whenever
- * EntropyMixer periodically runs to save a block of output from Linux RNG on
- * disk. This mixing is done in a way that does not increase the Linux RNG's
- * entropy estimate is not increased. This is to avoid having to trust/verify
- * the quality and authenticity of the "randomness" of the HW RNG.
- *
* <p>This class was modeled after the script in the
- * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html">
+ * <a href="https://man7.org/linux/man-pages/man4/random.4.html">
* random(4) manual page</a>.
*/
public class EntropyMixer extends Binder {
@@ -64,7 +56,6 @@
private static final long START_NANOTIME = System.nanoTime();
private final String randomDevice;
- private final String hwRandomDevice;
private final String entropyFile;
/**
@@ -80,7 +71,6 @@
Slog.e(TAG, "Will not process invalid message");
return;
}
- addHwRandomEntropy();
writeEntropy();
scheduleEntropyWriter();
}
@@ -94,25 +84,21 @@
};
public EntropyMixer(Context context) {
- this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random");
+ this(context, getSystemDir() + "/entropy.dat", "/dev/urandom");
}
/** Test only interface, not for public use */
public EntropyMixer(
Context context,
String entropyFile,
- String randomDevice,
- String hwRandomDevice) {
+ String randomDevice) {
if (randomDevice == null) { throw new NullPointerException("randomDevice"); }
- if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); }
if (entropyFile == null) { throw new NullPointerException("entropyFile"); }
this.randomDevice = randomDevice;
- this.hwRandomDevice = hwRandomDevice;
this.entropyFile = entropyFile;
loadInitialEntropy();
addDeviceSpecificEntropy();
- addHwRandomEntropy();
writeEntropy();
scheduleEntropyWriter();
IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
@@ -192,23 +178,6 @@
}
}
- /**
- * Mixes in the output from HW RNG (if present) into the Linux RNG.
- */
- private void addHwRandomEntropy() {
- if (!new File(hwRandomDevice).exists()) {
- // HW RNG not present/exposed -- ignore
- return;
- }
-
- try {
- RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false);
- Slog.i(TAG, "Added HW RNG output to entropy pool");
- } catch (IOException e) {
- Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e);
- }
- }
-
private static String getSystemDir() {
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
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/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 4dce59f..27210da 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -641,21 +641,34 @@
}
boolean isVcnManagedNetwork = false;
+ boolean isRestrictedCarrierWifi = false;
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
synchronized (mLock) {
ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);
Vcn vcn = mVcns.get(subGroup);
- if (vcn != null && vcn.isActive()) {
- isVcnManagedNetwork = true;
+ if (vcn != null) {
+ if (vcn.isActive()) {
+ isVcnManagedNetwork = true;
+ }
+
+ if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ // Carrier WiFi always restricted if VCN exists (even in safe mode).
+ isRestrictedCarrierWifi = true;
+ }
}
}
}
+
if (isVcnManagedNetwork) {
networkCapabilities.removeCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
}
+ if (isRestrictedCarrierWifi) {
+ networkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ }
+
return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9382e1a..f0f29a9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10700,13 +10700,13 @@
long kernelUsed = memInfo.getKernelUsedSizeKb();
final long ionHeap = Debug.getIonHeapsSizeKb();
final long ionPool = Debug.getIonPoolsSizeKb();
+ final long dmabufMapped = Debug.getDmabufMappedSizeKb();
if (ionHeap >= 0 && ionPool >= 0) {
- final long ionMapped = Debug.getIonMappedSizeKb();
- final long ionUnmapped = ionHeap - ionMapped;
+ final long ionUnmapped = ionHeap - dmabufMapped;
pw.print(" ION: ");
pw.print(stringifyKBSize(ionHeap + ionPool));
pw.print(" (");
- pw.print(stringifyKBSize(ionMapped));
+ pw.print(stringifyKBSize(dmabufMapped));
pw.print(" mapped + ");
pw.print(stringifyKBSize(ionUnmapped));
pw.print(" unmapped + ");
@@ -10715,11 +10715,34 @@
// Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
// set on ION VMAs, therefore consider the entire ION heap as used kernel memory
kernelUsed += ionHeap;
+ } else {
+ final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
+ if (totalExportedDmabuf >= 0) {
+ final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;
+ pw.print("DMA-BUF: ");
+ pw.print(stringifyKBSize(totalExportedDmabuf));
+ pw.print(" (");
+ pw.print(stringifyKBSize(dmabufMapped));
+ pw.print(" mapped + ");
+ pw.print(stringifyKBSize(dmabufUnmapped));
+ pw.println(" unmapped)");
+ kernelUsed += totalExportedDmabuf;
+ }
+ final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
+ if (totalDmabufHeapPool >= 0) {
+ pw.print("DMA-BUF Heaps pool: ");
+ pw.println(stringifyKBSize(totalDmabufHeapPool));
+ }
}
final long gpuUsage = Debug.getGpuTotalUsageKb();
if (gpuUsage >= 0) {
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().
+ */
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 8f11a5a..3ff5872 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1512,15 +1512,29 @@
final long ionHeap = Debug.getIonHeapsSizeKb();
final long ionPool = Debug.getIonPoolsSizeKb();
if (ionHeap >= 0 && ionPool >= 0) {
- final long ionMapped = Debug.getIonMappedSizeKb();
- final long ionUnmapped = ionHeap - ionMapped;
memInfoBuilder.append(" ION: ");
memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
memInfoBuilder.append("\n");
// Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
// set on ION VMAs, therefore consider the entire ION heap as used kernel memory
kernelUsed += ionHeap;
+ } else {
+ final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
+ if (totalExportedDmabuf >= 0) {
+ memInfoBuilder.append("DMA-BUF: ");
+ memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf));
+ memInfoBuilder.append("\n");
+ kernelUsed += totalExportedDmabuf;
+ }
+
+ final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
+ if (totalDmabufHeapPool >= 0) {
+ memInfoBuilder.append("DMA-BUF Heaps pool: ");
+ memInfoBuilder.append(stringifyKBSize(totalDmabufHeapPool));
+ memInfoBuilder.append("\n");
+ }
}
+
final long gpuUsage = Debug.getGpuTotalUsageKb();
if (gpuUsage >= 0) {
memInfoBuilder.append(" GPU: ");
@@ -1531,6 +1545,11 @@
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().
+ */
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..b994389 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: 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,12 +809,13 @@
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()
@@ -849,59 +823,48 @@
}
@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: 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: 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: Also populate typeToIds map
}
+ // TODO: Store typeToIds in mEnergyConsumerTypeToIdMap.
+ return idToConsumer;
}
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 773e313..6500f6d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -72,6 +72,7 @@
import com.android.internal.os.PowerProfile;
import com.android.internal.os.RailStats;
import com.android.internal.os.RpmStats;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ParseUtils;
@@ -126,15 +127,20 @@
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.replaceWith("?");
- private static final int MAX_LOW_POWER_STATS_SIZE = 4096;
+ private static final int MAX_LOW_POWER_STATS_SIZE = 8192;
private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
+ private static final String EMPTY = "Empty";
private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Object mLock = new Object();
+ private final Object mPowerStatsLock = new Object();
+ @GuardedBy("mPowerStatsLock")
private PowerStatsInternal mPowerStatsInternal = null;
+ @GuardedBy("mPowerStatsLock")
private Map<Integer, String> mEntityNames = new HashMap();
+ @GuardedBy("mPowerStatsLock")
private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
@GuardedBy("mStats")
@@ -172,13 +178,6 @@
};
private void populatePowerEntityMaps() {
- if (mPowerStatsInternal == null) {
- // PowerStatsInternal unavailable, don't bother populating maps.
- mEntityNames = null;
- mStateNames = null;
- return;
- }
-
PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
if (entities == null) {
return;
@@ -202,6 +201,12 @@
*/
@Override
public void fillLowPowerStats(RpmStats rpmStats) {
+ synchronized (mPowerStatsLock) {
+ if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) {
+ return;
+ }
+ }
+
final StateResidencyResult[] results;
try {
results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
@@ -237,16 +242,22 @@
@Override
public String getSubsystemLowPowerStats() {
+ synchronized (mPowerStatsLock) {
+ if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) {
+ return EMPTY;
+ }
+ }
+
final StateResidencyResult[] results;
try {
results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
.get(POWER_STATS_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Slog.e(TAG, "Failed to getStateResidencyAsync", e);
- return "Empty";
+ return EMPTY;
}
- if (results.length == 0) return "Empty";
+ if (results.length == 0) return EMPTY;
int charsLeft = MAX_LOW_POWER_STATS_SIZE;
StringBuilder builder = new StringBuilder("SubsystemPowerState");
@@ -322,9 +333,14 @@
} catch (RemoteException e) {
Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
}
- mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
- if (mPowerStatsInternal != null) {
- populatePowerEntityMaps();
+
+ synchronized (mPowerStatsLock) {
+ mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+ if (mPowerStatsInternal != null) {
+ populatePowerEntityMaps();
+ } else {
+ Slog.e(TAG, "Could not register PowerStatsInternal");
+ }
}
Watchdog.getInstance().addMonitor(this);
@@ -342,6 +358,11 @@
}
@Override
+ public SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes() {
+ return mStats.getSystemServiceCpuThreadTimes();
+ }
+
+ @Override
public void noteJobsDeferred(int uid, int numDeferred, long sinceLast) {
if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
BatteryStatsService.this.noteJobsDeferred(uid, numDeferred, sinceLast);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c18031f..7bdf43c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -538,6 +538,12 @@
private static native int getBinderFreezeInfo(int pid);
/**
+ * Returns the path to be checked to verify whether the freezer is supported by this system.
+ * @return absolute path to the file
+ */
+ private static native String getFreezerCheckPath();
+
+ /**
* Determines whether the freezer is supported by this system
*/
public static boolean isFreezerSupported() {
@@ -545,11 +551,15 @@
FileReader fr = null;
try {
- fr = new FileReader("/sys/fs/cgroup/uid_0/cgroup.freeze");
+ fr = new FileReader(getFreezerCheckPath());
char state = (char) fr.read();
if (state == '1' || state == '0') {
supported = true;
+ // This is a workaround after reverting the cgroup v2 uid/pid hierarchy due to
+ // http://b/179006802.
+ // TODO: remove once the uid/pid hierarchy is restored
+ enableFreezerInternal(true);
} else {
Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
}
@@ -1149,7 +1159,7 @@
}
return;
}
- } catch (IOException e) {
+ } catch (Exception e) {
Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
+ "): " + e);
return;
@@ -1244,7 +1254,7 @@
}
}
}
- } catch (IOException e) {
+ } catch (Exception e) {
Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
synchronized (mAm) {
synchronized (mProcLock) {
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/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 3acad49..76c3467 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -16,17 +16,23 @@
package com.android.server.app;
+import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameManager.GameMode;
import android.app.IGameManagerService;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -81,6 +87,14 @@
switch (msg.what) {
case WRITE_SETTINGS: {
final int userId = (int) msg.obj;
+ if (userId < 0) {
+ Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
+ synchronized (mLock) {
+ removeMessages(WRITE_SETTINGS, msg.obj);
+ }
+ break;
+ }
+
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
synchronized (mLock) {
removeMessages(WRITE_SETTINGS, msg.obj);
@@ -94,6 +108,15 @@
}
case REMOVE_SETTINGS: {
final int userId = (int) msg.obj;
+ if (userId < 0) {
+ Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
+ synchronized (mLock) {
+ removeMessages(WRITE_SETTINGS, msg.obj);
+ removeMessages(REMOVE_SETTINGS, msg.obj);
+ }
+ break;
+ }
+
synchronized (mLock) {
// Since the user was removed, ignore previous write message
// and do write here.
@@ -146,9 +169,23 @@
}
}
- //TODO(b/178111358) Add proper permission check and multi-user handling
+ private boolean hasPermission(String permission) {
+ return mContext.checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public @GameMode int getGameMode(String packageName, int userId) {
+ if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) {
+ Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE"));
+ return GameManager.GAME_MODE_UNSUPPORTED;
+ }
+
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getGameMode",
+ "com.android.server.app.GameManagerService");
+
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
return GameManager.GAME_MODE_UNSUPPORTED;
@@ -158,9 +195,18 @@
}
}
- //TODO(b/178111358) Add proper permission check and multi-user handling
@Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
+ if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) {
+ Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE"));
+ return;
+ }
+
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "setGameMode",
+ "com.android.server.app.GameManagerService");
+
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
return;
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 14292d9c..c956043 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -686,7 +686,8 @@
* @return true if this AuthSession is finished, e.g. should be set to null
*/
boolean onCancelAuthSession(boolean force) {
- final boolean authStarted = mState == STATE_AUTH_STARTED
+ final boolean authStarted = mState == STATE_AUTH_CALLED
+ || mState == STATE_AUTH_STARTED
|| mState == STATE_AUTH_STARTED_UI_SHOWING;
if (authStarted && !force) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 0536e78..b31a54b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -311,4 +311,9 @@
public int getProtoEnum() {
return BiometricsProto.CM_AUTHENTICATE;
}
+
+ @Override
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 8fa3bbb..81ce2d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -82,12 +82,18 @@
@NonNull protected Callback mCallback;
/**
- * Returns a ClientMonitorEnum constant defined in biometrics.proto
- * @return
+ * @return A ClientMonitorEnum constant defined in biometrics.proto
*/
public abstract int getProtoEnum();
/**
+ * @return True if the ClientMonitor should cancel any current and pending interruptable clients
+ */
+ public boolean interruptsPrecedingClients() {
+ return false;
+ }
+
+ /**
* @param context system_server context
* @param token a unique token for the client
* @param listener recipient of related events (e.g. authentication)
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index c5237ab..20c25c3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -475,14 +475,17 @@
*/
public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback clientCallback) {
- // Mark any interruptable pending clients as canceling. Once they reach the head of the
- // queue, the scheduler will send ERROR_CANCELED and skip the operation.
- for (Operation operation : mPendingOperations) {
- if (operation.mClientMonitor instanceof Interruptable
- && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
- Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
- + operation.mClientMonitor);
- operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+ // If the incoming operation should interrupt preceding clients, mark any interruptable
+ // pending clients as canceling. Once they reach the head of the queue, the scheduler will
+ // send ERROR_CANCELED and skip the operation.
+ if (clientMonitor.interruptsPrecedingClients()) {
+ for (Operation operation : mPendingOperations) {
+ if (operation.mClientMonitor instanceof Interruptable
+ && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+ Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+ + operation.mClientMonitor);
+ operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+ }
}
}
@@ -490,8 +493,11 @@
Slog.d(getTag(), "[Added] " + clientMonitor
+ ", new queue size: " + mPendingOperations.size());
- // If the current operation is cancellable, start the cancellation process.
- if (mCurrentOperation != null && mCurrentOperation.mClientMonitor instanceof Interruptable
+ // If the new operation should interrupt preceding clients, and if the current operation is
+ // cancellable, start the cancellation process.
+ if (clientMonitor.interruptsPrecedingClients()
+ && mCurrentOperation != null
+ && mCurrentOperation.mClientMonitor instanceof Interruptable
&& mCurrentOperation.mState == Operation.STATE_STARTED) {
Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index da76af8..25b7add 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -16,9 +16,12 @@
package com.android.server.biometrics.sensors;
+import android.annotation.NonNull;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -174,4 +177,33 @@
mFingerprintServiceReceiver.onUdfpsPointerUp(sensorId);
}
}
+
+ // Face-specific callbacks for FaceManager only
+
+ /**
+ * Called each time a new frame is received during face authentication.
+ *
+ * @param frame Information about the current frame.
+ *
+ * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
+ */
+ public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame)
+ throws RemoteException {
+ if (mFaceServiceReceiver != null) {
+ mFaceServiceReceiver.onAuthenticationFrame(frame);
+ }
+ }
+
+ /**
+ * Called each time a new frame is received during face enrollment.
+ *
+ * @param frame Information about the current frame.
+ *
+ * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
+ */
+ public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) throws RemoteException {
+ if (mFaceServiceReceiver != null) {
+ mFaceServiceReceiver.onEnrollmentFrame(frame);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 8d81016..e1320d8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -113,4 +113,9 @@
public int getProtoEnum() {
return BiometricsProto.CM_ENROLL;
}
+
+ @Override
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index ce24e5e..de57186 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -69,6 +69,8 @@
final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates();
+ Slog.d(TAG, "Enumerate onClientFinished: " + clientMonitor + ", success: " + success);
+
if (!unknownHALTemplates.isEmpty()) {
Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion");
}
@@ -90,6 +92,7 @@
private final Callback mRemoveCallback = new Callback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+ Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success);
mCallback.onClientFinished(InternalCleanupClient.this, success);
}
};
@@ -115,6 +118,8 @@
}
private void startCleanupUnknownHalTemplates() {
+ Slog.d(TAG, "startCleanupUnknownHalTemplates, size: " + mUnknownHALTemplates.size());
+
UserTemplate template = mUnknownHALTemplates.get(0);
mUnknownHALTemplates.remove(template);
mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
@@ -138,6 +143,8 @@
// Start enumeration. Removal will start if necessary, when enumeration is completed.
mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
+
+ Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
mCurrentTask.start(mEnumerateCallback);
}
@@ -165,6 +172,7 @@
+ mCurrentTask.getClass().getSimpleName());
return;
}
+ Slog.d(TAG, "onEnumerated, remaining: " + remaining);
((EnumerateConsumer) mCurrentTask).onEnumerationResult(identifier, remaining);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 9d19fdf..e3feb74 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -82,6 +82,7 @@
private void handleEnumeratedTemplate(BiometricAuthenticator.Identifier identifier) {
if (identifier == null) {
+ Slog.d(TAG, "Null identifier");
return;
}
Slog.v(TAG, "handleEnumeratedTemplate: " + identifier.getBiometricId());
@@ -103,6 +104,7 @@
private void doTemplateCleanup() {
if (mEnrolledList == null) {
+ Slog.d(TAG, "Null enrolledList");
return;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index d2673d2..897ebd7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -24,6 +24,8 @@
import android.hardware.biometrics.face.AuthenticationFrame;
import android.hardware.biometrics.face.BaseFrame;
import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.util.Slog;
@@ -117,6 +119,16 @@
public void onChallengeInterruptFinished(int sensorId) {
}
+
+ @Override
+ public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+
+ }
+
+ @Override
+ public void onEnrollmentFrame(FaceEnrollFrame frame) {
+
+ }
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId,
@@ -183,7 +195,7 @@
mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
}
- // TODO(b/174619156): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
+ // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
@Override
public void notifyAcquired(int userId, int acquireInfo) {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
@@ -194,7 +206,7 @@
AuthenticationFrame authenticationFrame = new AuthenticationFrame();
authenticationFrame.data = data;
- // TODO(b/174619156): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
+ // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
// This will need to call the correct callback once the onAcquired callback is removed.
mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFrame(
authenticationFrame);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 3057766..8f55402 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -29,7 +29,6 @@
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.FaceAuthenticationFrame;
-import android.hardware.face.FaceDataFrame;
import android.hardware.face.FaceManager;
import android.os.IBinder;
import android.os.RemoteException;
@@ -179,19 +178,16 @@
return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
}
- private boolean shouldSend(int acquireInfo, int vendorCode) {
- if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
- return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
- } else {
- return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
- }
+ private boolean shouldSendAcquiredMessage(int acquireInfo, int vendorCode) {
+ return acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR
+ ? !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode)
+ : !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
}
@Override
public void onAcquired(int acquireInfo, int vendorCode) {
mLastAcquire = acquireInfo;
-
- final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
+ final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
}
@@ -201,9 +197,21 @@
* @param frame Information about the current frame.
*/
public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame) {
- // TODO(b/178414967): Send additional frame data to the client callback.
- final FaceDataFrame data = frame.getData();
- onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+ // Log acquisition but don't send it to the client yet, since that's handled below.
+ final int acquireInfo = frame.getData().getAcquiredInfo();
+ final int vendorCode = frame.getData().getVendorCode();
+ mLastAcquire = acquireInfo;
+ onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
+
+ final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
+ if (shouldSend && getListener() != null) {
+ try {
+ getListener().onAuthenticationFrame(frame);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to send authentication frame", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
}
@Override public void onLockoutTimed(long durationMillis) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index da657b9..898d81b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -27,7 +27,6 @@
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
-import android.hardware.face.FaceDataFrame;
import android.hardware.face.FaceEnrollFrame;
import android.hardware.face.FaceManager;
import android.os.IBinder;
@@ -101,14 +100,15 @@
getTargetUserId()).size() >= mMaxTemplatesPerUser;
}
+ private boolean shouldSendAcquiredMessage(int acquireInfo, int vendorCode) {
+ return acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR
+ ? !Utils.listContains(mEnrollIgnoreListVendor, vendorCode)
+ : !Utils.listContains(mEnrollIgnoreList, acquireInfo);
+ }
+
@Override
public void onAcquired(int acquireInfo, int vendorCode) {
- final boolean shouldSend;
- if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
- shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
- } else {
- shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
- }
+ final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
}
@@ -118,9 +118,20 @@
* @param frame Information about the current frame.
*/
public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) {
- // TODO(b/178414967): Send additional frame data to the client callback.
- final FaceDataFrame data = frame.getData();
- onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+ // Log acquisition but don't send it to the client yet, since that's handled below.
+ final int acquireInfo = frame.getData().getAcquiredInfo();
+ final int vendorCode = frame.getData().getVendorCode();
+ onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
+
+ final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
+ if (shouldSend && getListener() != null) {
+ try {
+ getListener().onEnrollmentFrame(frame);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to send enrollment frame", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index baeb3fd..81e90df 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
@@ -496,6 +496,7 @@
}
void setTestHalEnabled(boolean enabled) {
+ Slog.w(mTag, "setTestHalEnabled: " + enabled);
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..a38da3a 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
@@ -23,86 +23,85 @@
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.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() {
+ return new ISession.Stub() {
@Override
public void generateChallenge(int cookie, int timeoutSec) {
-
+ Slog.w(TAG, "generateChallenge, cookie: " + cookie);
}
@Override
public void revokeChallenge(int cookie, long challenge) {
-
+ Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
}
@Override
public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
byte enrollmentType, byte[] features, NativeHandle previewSurface) {
+ Slog.w(TAG, "enroll, cookie: " + cookie);
return null;
}
@Override
public ICancellationSignal authenticate(int cookie, long operationId) {
+ Slog.w(TAG, "authenticate, cookie: " + cookie);
return null;
}
@Override
public ICancellationSignal detectInteraction(int cookie) {
+ Slog.w(TAG, "detectInteraction, cookie: " + cookie);
return null;
}
@Override
public void enumerateEnrollments(int cookie) {
-
+ Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
}
@Override
public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+ Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
}
@Override
public void getFeatures(int cookie, int enrollmentId) {
-
+ Slog.w(TAG, "getFeatures, cookie: " + cookie);
}
@Override
public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
byte feature, boolean enabled) {
-
+ Slog.w(TAG, "setFeature, cookie: " + cookie);
}
@Override
public void getAuthenticatorId(int cookie) {
-
+ Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
}
@Override
public void invalidateAuthenticatorId(int cookie) {
-
+ Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
}
@Override
public void resetLockout(int cookie, HardwareAuthToken hat) {
-
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
+ Slog.w(TAG, "resetLockout, cookie: " + cookie);
}
};
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 4142a52..d519d60 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -22,6 +22,8 @@
import android.content.Context;
import android.hardware.biometrics.ITestSession;
import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.util.Slog;
@@ -106,6 +108,16 @@
public void onChallengeInterruptFinished(int sensorId) {
}
+
+ @Override
+ public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+
+ }
+
+ @Override
+ public void onEnrollmentFrame(FaceEnrollFrame frame) {
+
+ }
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId, @NonNull Face10 face10,
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..00ca802 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;
}
@@ -95,16 +99,19 @@
@Override
public int enumerate() {
+ Slog.w(TAG, "enumerate");
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 +122,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/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 9611192..bcd1b8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -98,4 +98,9 @@
public int getProtoEnum() {
return BiometricsProto.CM_DETECT_INTERACTION;
}
+
+ @Override
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
}
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..73b59cf 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
@@ -476,6 +476,7 @@
}
void setTestHalEnabled(boolean enabled) {
+ Slog.w(mTag, "setTestHalEnabled, enabled");
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..66b68ee 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
@@ -22,89 +22,89 @@
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.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() {
+ return new ISession.Stub() {
@Override
public void generateChallenge(int cookie, int timeoutSec) {
-
+ Slog.w(TAG, "generateChallenge, cookie: " + cookie);
}
@Override
public void revokeChallenge(int cookie, long challenge) {
-
+ Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
}
@Override
public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
+ Slog.w(TAG, "enroll, cookie: " + cookie);
return null;
}
@Override
public ICancellationSignal authenticate(int cookie, long operationId) {
+ Slog.w(TAG, "authenticate, cookie: " + cookie);
return null;
}
@Override
public ICancellationSignal detectInteraction(int cookie) {
+ Slog.w(TAG, "detectInteraction, cookie: " + cookie);
return null;
}
@Override
public void enumerateEnrollments(int cookie) {
-
+ Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
}
@Override
public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+ Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
}
@Override
public void getAuthenticatorId(int cookie) {
-
+ Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
}
@Override
public void invalidateAuthenticatorId(int cookie) {
-
+ Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
}
@Override
public void resetLockout(int cookie, HardwareAuthToken hat) {
-
+ Slog.w(TAG, "resetLockout, cookie: " + cookie);
}
@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/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 7989dca..8acb284 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -130,4 +130,9 @@
public int getProtoEnum() {
return BiometricsProto.CM_DETECT_INTERACTION;
}
+
+ @Override
+ public boolean interruptsPrecedingClients() {
+ return true;
+ }
}
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..57447f3 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;
}
@@ -80,11 +84,13 @@
@Override
public int enumerate() {
+ Slog.w(TAG, "Enumerate");
return 0;
}
@Override
public int remove(int gid, int fid) {
+ Slog.w(TAG, "Remove");
return 0;
}
@@ -95,6 +101,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/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index bff1a5c..c05e253 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -717,8 +717,9 @@
mNumBackgroundNetworkRequests += delta;
break;
- case TRACK_DEFAULT:
case LISTEN:
+ case TRACK_DEFAULT:
+ case TRACK_SYSTEM_DEFAULT:
break;
case NONE:
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/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/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index fc2c7e0..33c19b1 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -74,6 +74,7 @@
import android.net.UnderlyingNetworkInfo;
import android.net.VpnManager;
import android.net.VpnService;
+import android.net.VpnTransportInfo;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.ChildSessionParams;
@@ -435,6 +436,7 @@
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
loadAlwaysOnPackage(keyStore);
}
@@ -929,6 +931,7 @@
jniReset(mInterface);
mInterface = null;
mNetworkCapabilities.setUids(null);
+ mNetworkCapabilities.setTransportInfo(null);
}
// Revoke the connection or stop the VpnRunner.
@@ -999,6 +1002,8 @@
case VpnManager.TYPE_VPN_SERVICE:
toChange = new String[] {AppOpsManager.OPSTR_ACTIVATE_VPN};
break;
+ case VpnManager.TYPE_VPN_LEGACY:
+ return false;
default:
Log.wtf(TAG, "Unrecognized VPN type while granting authorization");
return false;
@@ -1029,6 +1034,8 @@
return isVpnServicePreConsented(context, packageName);
case VpnManager.TYPE_VPN_PLATFORM:
return isVpnProfilePreConsented(context, packageName);
+ case VpnManager.TYPE_VPN_LEGACY:
+ return VpnConfig.LEGACY_VPN.equals(packageName);
default:
return false;
}
@@ -1211,6 +1218,8 @@
mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId,
mConfig.allowedApplications, mConfig.disallowedApplications));
+ mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
+
// Only apps targeting Q and above can explicitly declare themselves as metered.
// These VPNs are assumed metered unless they state otherwise.
if (mIsPackageTargetingAtLeastQ && mConfig.isMetered) {
@@ -1736,6 +1745,7 @@
private void cleanupVpnStateLocked() {
mStatusIntent = null;
mNetworkCapabilities.setUids(null);
+ mNetworkCapabilities.setTransportInfo(null);
mConfig = null;
mInterface = null;
@@ -1846,22 +1856,18 @@
}
/**
- * Gets the currently running App-based VPN type
+ * Gets the currently running VPN type
*
- * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an
- * app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always
+ * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running a
+ * VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always
* Settings-based, the Platform VPNs can be initiated by both apps and Settings.
*/
- public synchronized int getActiveAppVpnType() {
- if (VpnConfig.LEGACY_VPN.equals(mPackage)) {
- return VpnManager.TYPE_VPN_NONE;
- }
-
- if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) {
- return VpnManager.TYPE_VPN_PLATFORM;
- } else {
- return VpnManager.TYPE_VPN_SERVICE;
- }
+ public synchronized int getActiveVpnType() {
+ if (!mNetworkInfo.isConnectedOrConnecting()) return VpnManager.TYPE_VPN_NONE;
+ if (mVpnRunner == null) return VpnManager.TYPE_VPN_SERVICE;
+ return mVpnRunner instanceof IkeV2VpnRunner
+ ? VpnManager.TYPE_VPN_PLATFORM
+ : VpnManager.TYPE_VPN_LEGACY;
}
private void updateAlwaysOnNotification(DetailedState networkState) {
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/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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee56..dce6375 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;
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/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/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/DevicePowerStatusAction.java b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
index 1a0a639..7b646b3 100644
--- a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
@@ -17,6 +17,7 @@
*/
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.DisplayStatusCallback;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -65,6 +66,20 @@
@Override
boolean start() {
+ HdmiControlService service = localDevice().mService;
+ if (service.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ HdmiDeviceInfo deviceInfo = service.getHdmiCecNetwork().getCecDeviceInfo(
+ mTargetAddress);
+ if (deviceInfo != null
+ && deviceInfo.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ int powerStatus = deviceInfo.getDevicePowerStatus();
+ if (powerStatus != HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ invokeCallback(powerStatus);
+ finish();
+ return true;
+ }
+ }
+ }
queryDevicePowerStatus();
mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 75b52f9..2995252 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -239,6 +239,7 @@
@ServiceThreadOnly
protected void onActiveSourceLost() {
assertRunOnServiceThread();
+ mService.pauseActiveMediaSessions();
switch (mService.getHdmiCecConfig().getStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index bdf92ca..fa1fb48 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -56,6 +56,8 @@
import android.hardware.tv.cec.V1_0.OptionKey;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.net.Uri;
@@ -3294,6 +3296,16 @@
}
}
+ @VisibleForTesting
+ void pauseActiveMediaSessions() {
+ MediaSessionManager mediaSessionManager = getContext()
+ .getSystemService(MediaSessionManager.class);
+ List<MediaController> mediaControllers = mediaSessionManager.getActiveSessions(null);
+ for (MediaController mediaController : mediaControllers) {
+ mediaController.getTransportControls().pause();
+ }
+ }
+
void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
synchronized (mLock) {
mActiveSource.logicalAddress = logicalAddress;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1a4c8b7..00ae30e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5246,12 +5246,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 ea759bf..47cb43e 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -79,6 +79,7 @@
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.stats.location.LocationStatsEnums;
+import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -87,6 +88,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssConfiguration;
@@ -98,7 +100,6 @@
import com.android.server.location.injector.EmergencyHelper;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationAttributionHelper;
-import com.android.server.location.injector.LocationEventLog;
import com.android.server.location.injector.LocationPermissionsHelper;
import com.android.server.location.injector.LocationPowerSaveModeHelper;
import com.android.server.location.injector.LocationUsageLogger;
@@ -147,9 +148,10 @@
public Lifecycle(Context context) {
super(context);
+ LocationEventLog eventLog = new LocationEventLog();
mUserInfoHelper = new LifecycleUserInfoHelper(context);
- mSystemInjector = new SystemInjector(context, mUserInfoHelper);
- mService = new LocationManagerService(context, mSystemInjector);
+ mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog);
+ mService = new LocationManagerService(context, mSystemInjector, eventLog);
}
@Override
@@ -159,7 +161,7 @@
// client caching behavior is only enabled after seeing the first invalidate
LocationManager.invalidateLocalLocationEnabledCaches();
// disable caching for our own process
- Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class))
+ Objects.requireNonNull(getContext().getSystemService(LocationManager.class))
.disableLocalLocationEnabledCaches();
}
@@ -221,6 +223,7 @@
private final Context mContext;
private final Injector mInjector;
+ private final LocationEventLog mEventLog;
private final LocalService mLocalService;
private final GeofenceManager mGeofenceManager;
@@ -245,10 +248,10 @@
private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
new CopyOnWriteArrayList<>();
- LocationManagerService(Context context, Injector injector) {
+ LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mInjector = injector;
-
+ mEventLog = eventLog;
mLocalService = new LocalService();
LocalServices.addService(LocationManagerInternal.class, mLocalService);
@@ -256,7 +259,7 @@
// set up passive provider first since it will be required for all other location providers,
// which are loaded later once the system is ready.
- mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
+ mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog);
addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext));
// TODO: load the gps provider here as well, which will require refactoring
@@ -297,7 +300,7 @@
}
LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
- providerName, mPassiveManager);
+ mEventLog, providerName, mPassiveManager);
addLocationProviderManager(manager, null);
return manager;
}
@@ -341,7 +344,7 @@
com.android.internal.R.string.config_networkLocationProviderPackageName);
if (networkProvider != null) {
LocationProviderManager networkManager = new LocationProviderManager(mContext,
- mInjector, NETWORK_PROVIDER, mPassiveManager);
+ mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager);
addLocationProviderManager(networkManager, networkProvider);
} else {
Log.w(TAG, "no network location provider found");
@@ -360,7 +363,7 @@
com.android.internal.R.string.config_fusedLocationProviderPackageName);
if (fusedProvider != null) {
LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
- FUSED_PROVIDER, mPassiveManager);
+ mEventLog, FUSED_PROVIDER, mPassiveManager);
addLocationProviderManager(fusedManager, fusedProvider);
} else {
Log.wtf(TAG, "no fused location provider found");
@@ -375,7 +378,7 @@
mGnssManagerService.onSystemReady();
LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
- GPS_PROVIDER, mPassiveManager);
+ mEventLog, GPS_PROVIDER, mPassiveManager);
addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
}
@@ -431,7 +434,7 @@
Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
}
- mInjector.getLocationEventLog().logLocationEnabled(userId, enabled);
+ mEventLog.logLocationEnabled(userId, enabled);
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -1193,9 +1196,27 @@
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) {
- mGnssManagerService.dump(fd, ipw, args);
- return;
+ if (args.length > 0) {
+ LocationProviderManager manager = getLocationProviderManager(args[0]);
+ if (manager != null) {
+ ipw.println("Provider:");
+ ipw.increaseIndent();
+ manager.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+
+ ipw.println("Event Log:");
+ ipw.increaseIndent();
+ mEventLog.iterate(manager.getName(), ipw::println);
+ ipw.decreaseIndent();
+ return;
+ }
+
+ if ("--gnssmetrics".equals(args[0])) {
+ if (mGnssManagerService != null) {
+ mGnssManagerService.dump(fd, ipw, args);
+ }
+ return;
+ }
}
ipw.println("Location Manager State:");
@@ -1227,6 +1248,26 @@
}
ipw.decreaseIndent();
+ ipw.println("Historical Aggregate Location Provider Data:");
+ ipw.increaseIndent();
+ ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats =
+ mEventLog.copyAggregateStats();
+ for (int i = 0; i < aggregateStats.size(); i++) {
+ ipw.print(aggregateStats.keyAt(i));
+ ipw.println(":");
+ ipw.increaseIndent();
+ ArrayMap<String, LocationEventLog.AggregateStats> providerStats =
+ aggregateStats.valueAt(i);
+ for (int j = 0; j < providerStats.size(); j++) {
+ ipw.print(providerStats.keyAt(j));
+ ipw.print(": ");
+ providerStats.valueAt(j).updateTotals();
+ ipw.println(providerStats.valueAt(j));
+ }
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+
if (mGnssManagerService != null) {
ipw.println("GNSS Manager:");
ipw.increaseIndent();
@@ -1241,7 +1282,7 @@
ipw.println("Event Log:");
ipw.increaseIndent();
- mInjector.getLocationEventLog().iterate(ipw::println);
+ mEventLog.iterate(ipw::println);
ipw.decreaseIndent();
}
@@ -1320,7 +1361,6 @@
private final Context mContext;
private final UserInfoHelper mUserInfoHelper;
- private final LocationEventLog mLocationEventLog;
private final AlarmHelper mAlarmHelper;
private final SystemAppOpsHelper mAppOpsHelper;
private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
@@ -1339,19 +1379,17 @@
@GuardedBy("this")
private boolean mSystemReady;
- SystemInjector(Context context, UserInfoHelper userInfoHelper) {
+ SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) {
mContext = context;
mUserInfoHelper = userInfoHelper;
- mLocationEventLog = new LocationEventLog();
mAlarmHelper = new SystemAlarmHelper(context);
mAppOpsHelper = new SystemAppOpsHelper(context);
mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
mAppOpsHelper);
mSettingsHelper = new SystemSettingsHelper(context);
mAppForegroundHelper = new SystemAppForegroundHelper(context);
- mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context,
- mLocationEventLog);
+ mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mLocationUsageLogger = new LocationUsageLogger();
@@ -1430,11 +1468,6 @@
}
@Override
- public LocationEventLog getLocationEventLog() {
- return mLocationEventLog;
- }
-
- @Override
public LocationUsageLogger getLocationUsageLogger() {
return mLocationUsageLogger;
}
diff --git a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
index b5746bb..12dd3e6 100644
--- a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
@@ -16,12 +16,13 @@
package com.android.server.location.eventlog;
+import android.annotation.Nullable;
import android.os.SystemClock;
import android.util.TimeUtils;
import com.android.internal.util.Preconditions;
-import java.util.ListIterator;
+import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
@@ -35,6 +36,7 @@
boolean isFiller();
long getTimeDeltaMs();
String getLogString();
+ boolean filter(@Nullable String filter);
}
private static final class FillerEvent implements Log {
@@ -62,6 +64,11 @@
public String getLogString() {
throw new AssertionError();
}
+
+ @Override
+ public boolean filter(String filter) {
+ return false;
+ }
}
/**
@@ -87,6 +94,11 @@
public final long getTimeDeltaMs() {
return Integer.toUnsignedLong(mTimeDelta);
}
+
+ @Override
+ public boolean filter(String filter) {
+ return false;
+ }
}
// circular buffer of log entries
@@ -198,6 +210,17 @@
}
}
+ /**
+ * Iterates over the event log, passing each filter-matching log string to the given
+ * consumer.
+ */
+ public synchronized void iterate(String filter, Consumer<String> consumer) {
+ LogIterator it = new LogIterator(filter);
+ while (it.hasNext()) {
+ consumer.accept(it.next());
+ }
+ }
+
// returns the index of the first element
private int startIndex() {
return wrapIndex(mLogEndIndex - mLogSize);
@@ -205,12 +228,13 @@
// returns the index after this one
private int incrementIndex(int index) {
- return wrapIndex(index + 1);
- }
-
- // returns the index before this one
- private int decrementIndex(int index) {
- return wrapIndex(index - 1);
+ if (index == -1) {
+ return startIndex();
+ } else if (index >= 0) {
+ return wrapIndex(index + 1);
+ } else {
+ throw new IllegalArgumentException();
+ }
}
// rolls over the given index if necessary
@@ -219,7 +243,9 @@
return (index % mLog.length + mLog.length) % mLog.length;
}
- private class LogIterator implements ListIterator<String> {
+ private class LogIterator implements Iterator<String> {
+
+ private final @Nullable String mFilter;
private final long mSystemTimeDeltaMs;
@@ -228,10 +254,17 @@
private int mCount;
LogIterator() {
+ this(null);
+ }
+
+ LogIterator(@Nullable String filter) {
+ mFilter = filter;
mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime();
mCurrentRealtimeMs = mStartRealtimeMs;
- mIndex = startIndex();
- mCount = 0;
+ mIndex = -1;
+ mCount = -1;
+
+ increment();
}
@Override
@@ -239,75 +272,17 @@
return mCount < mLogSize;
}
- @Override
- public boolean hasPrevious() {
- return mCount > 0;
- }
-
- @Override
- // return then increment
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Log log = mLog[mIndex];
- long nextDeltaMs = log.getTimeDeltaMs();
- long realtimeMs = mCurrentRealtimeMs + nextDeltaMs;
+ long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs;
- // calculate next index, skipping filler events
- do {
- mCurrentRealtimeMs += nextDeltaMs;
- mIndex = incrementIndex(mIndex);
- if (++mCount < mLogSize) {
- nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
- }
- } while (mCount < mLogSize && mLog[mIndex].isFiller());
+ increment();
- return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
- }
-
- @Override
- // decrement then return
- public String previous() {
- Log log;
- long currentDeltaMs;
- long realtimeMs;
-
- // calculate previous index, skipping filler events with MAX_TIME_DELTA
- do {
- if (!hasPrevious()) {
- throw new NoSuchElementException();
- }
-
- mIndex = decrementIndex(mIndex);
- mCount--;
-
- log = mLog[mIndex];
- realtimeMs = mCurrentRealtimeMs;
-
- if (mCount > 0) {
- currentDeltaMs = log.getTimeDeltaMs();
- mCurrentRealtimeMs -= currentDeltaMs;
- }
- } while (mCount >= 0 && log.isFiller());
-
- return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
- }
-
- @Override
- public int nextIndex() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int previousIndex() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void add(String s) {
- throw new UnsupportedOperationException();
+ return getTimePrefix(timeMs) + log.getLogString();
}
@Override
@@ -315,9 +290,16 @@
throw new UnsupportedOperationException();
}
- @Override
- public void set(String s) {
- throw new UnsupportedOperationException();
+ private void increment() {
+ long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs();
+ do {
+ mCurrentRealtimeMs += nextDeltaMs;
+ mIndex = incrementIndex(mIndex);
+ if (++mCount < mLogSize) {
+ nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
+ }
+ } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null
+ && !mLog[mIndex].filter(mFilter))));
}
}
}
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
new file mode 100644
index 0000000..865d41f
--- /dev/null
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -0,0 +1,510 @@
+/*
+ * 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.location.eventlog;
+
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static android.util.TimeUtils.formatDuration;
+
+import static com.android.server.location.LocationManagerService.D;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.location.LocationRequest;
+import android.location.provider.ProviderRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Build;
+import android.os.PowerManager.LocationPowerSaveMode;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+/** In memory event log for location events. */
+public class LocationEventLog extends LocalEventLog {
+
+ private static int getLogSize() {
+ if (Build.IS_DEBUGGABLE || D) {
+ return 500;
+ } else {
+ return 200;
+ }
+ }
+
+ private static final int EVENT_LOCATION_ENABLED = 1;
+ private static final int EVENT_PROVIDER_ENABLED = 2;
+ private static final int EVENT_PROVIDER_MOCKED = 3;
+ private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
+ private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
+ private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
+ private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
+ private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
+ private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
+
+ @GuardedBy("mAggregateStats")
+ private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats;
+
+ public LocationEventLog() {
+ super(getLogSize());
+ mAggregateStats = new ArrayMap<>(4);
+ }
+
+ public ArrayMap<String, ArrayMap<String, AggregateStats>> copyAggregateStats() {
+ synchronized (mAggregateStats) {
+ ArrayMap<String, ArrayMap<String, AggregateStats>> copy = new ArrayMap<>(
+ mAggregateStats);
+ for (int i = 0; i < copy.size(); i++) {
+ copy.setValueAt(i, new ArrayMap<>(copy.valueAt(i)));
+ }
+ return copy;
+ }
+ }
+
+ private AggregateStats getAggregateStats(String provider, String packageName) {
+ synchronized (mAggregateStats) {
+ ArrayMap<String, AggregateStats> packageMap = mAggregateStats.get(provider);
+ if (packageMap == null) {
+ packageMap = new ArrayMap<>(2);
+ mAggregateStats.put(provider, packageMap);
+ }
+ AggregateStats stats = packageMap.get(packageName);
+ if (stats == null) {
+ stats = new AggregateStats();
+ packageMap.put(packageName, stats);
+ }
+ return stats;
+ }
+ }
+
+ /** Logs a location enabled/disabled event. */
+ public void logLocationEnabled(int userId, boolean enabled) {
+ addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+ }
+
+ /** Logs a location provider enabled/disabled event. */
+ public void logProviderEnabled(String provider, int userId, boolean enabled) {
+ addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
+ }
+
+ /** Logs a location provider being replaced/unreplaced by a mock provider. */
+ public void logProviderMocked(String provider, boolean mocked) {
+ addLogEvent(EVENT_PROVIDER_MOCKED, provider, mocked);
+ }
+
+ /** Logs a new client registration for a location provider. */
+ public void logProviderClientRegistered(String provider, CallerIdentity identity,
+ LocationRequest request) {
+ addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
+ getAggregateStats(provider, identity.getPackageName())
+ .markRequestAdded(request.getIntervalMillis());
+ }
+
+ /** Logs a client unregistration for a location provider. */
+ public void logProviderClientUnregistered(String provider, CallerIdentity identity) {
+ addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
+ getAggregateStats(provider, identity.getPackageName()).markRequestRemoved();
+ }
+
+ /** Logs a client for a location provider entering the active state. */
+ public void logProviderClientActive(String provider, CallerIdentity identity) {
+ getAggregateStats(provider, identity.getPackageName()).markRequestActive();
+ }
+
+ /** Logs a client for a location provider leaving the active state. */
+ public void logProviderClientInactive(String provider, CallerIdentity identity) {
+ getAggregateStats(provider, identity.getPackageName()).markRequestInactive();
+ }
+
+ /** Logs a client for a location provider entering the foreground state. */
+ public void logProviderClientForeground(String provider, CallerIdentity identity) {
+ getAggregateStats(provider, identity.getPackageName()).markRequestForeground();
+ }
+
+ /** Logs a client for a location provider leaving the foreground state. */
+ public void logProviderClientBackground(String provider, CallerIdentity identity) {
+ getAggregateStats(provider, identity.getPackageName()).markRequestBackground();
+ }
+
+ /** Logs a change to the provider request for a location provider. */
+ public void logProviderUpdateRequest(String provider, ProviderRequest request) {
+ addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
+ }
+
+ /** Logs a new incoming location for a location provider. */
+ public void logProviderReceivedLocations(String provider, int numLocations) {
+ if (Build.IS_DEBUGGABLE || D) {
+ addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider, numLocations);
+ }
+ }
+
+ /** Logs a location deliver for a client of a location provider. */
+ public void logProviderDeliveredLocations(String provider, int numLocations,
+ CallerIdentity identity) {
+ 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. */
+ public void logLocationPowerSaveMode(
+ @LocationPowerSaveMode int locationPowerSaveMode) {
+ addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, locationPowerSaveMode);
+ }
+
+ @Override
+ protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
+ switch (event) {
+ case EVENT_LOCATION_ENABLED:
+ return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+ case EVENT_PROVIDER_ENABLED:
+ return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
+ (Boolean) args[2]);
+ case EVENT_PROVIDER_MOCKED:
+ return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
+ case EVENT_PROVIDER_REGISTER_CLIENT:
+ return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
+ (CallerIdentity) args[1], (LocationRequest) args[2]);
+ case EVENT_PROVIDER_UNREGISTER_CLIENT:
+ return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
+ (CallerIdentity) args[1], null);
+ case EVENT_PROVIDER_UPDATE_REQUEST:
+ return new ProviderUpdateEvent(timeDelta, (String) args[0],
+ (ProviderRequest) args[1]);
+ case EVENT_PROVIDER_RECEIVE_LOCATION:
+ return new ProviderReceiveLocationEvent(timeDelta, (String) args[0],
+ (Integer) args[1]);
+ case EVENT_PROVIDER_DELIVER_LOCATION:
+ return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
+ (Integer) args[1], (CallerIdentity) args[2]);
+ case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
+ return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private abstract static class ProviderEvent extends LogEvent {
+
+ protected final String mProvider;
+
+ protected ProviderEvent(long timeDelta, String provider) {
+ super(timeDelta);
+ mProvider = provider;
+ }
+
+ @Override
+ public boolean filter(String filter) {
+ return mProvider.equals(filter);
+ }
+ }
+
+ private static final class ProviderEnabledEvent extends ProviderEvent {
+
+ private final int mUserId;
+ private final boolean mEnabled;
+
+ protected ProviderEnabledEvent(long timeDelta, String provider, int userId,
+ boolean enabled) {
+ super(timeDelta, provider);
+ mUserId = userId;
+ mEnabled = enabled;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
+ : "disabled");
+ }
+ }
+
+ private static final class ProviderMockedEvent extends ProviderEvent {
+
+ private final boolean mMocked;
+
+ protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) {
+ super(timeDelta, provider);
+ mMocked = mocked;
+ }
+
+ @Override
+ public String getLogString() {
+ if (mMocked) {
+ return mProvider + " provider added mock provider override";
+ } else {
+ return mProvider + " provider removed mock provider override";
+ }
+ }
+ }
+
+ private static final class ProviderRegisterEvent extends ProviderEvent {
+
+ private final boolean mRegistered;
+ private final CallerIdentity mIdentity;
+ @Nullable private final LocationRequest mLocationRequest;
+
+ private ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
+ CallerIdentity identity, @Nullable LocationRequest locationRequest) {
+ super(timeDelta, provider);
+ mRegistered = registered;
+ mIdentity = identity;
+ mLocationRequest = locationRequest;
+ }
+
+ @Override
+ public String getLogString() {
+ if (mRegistered) {
+ return mProvider + " provider " + "+registration " + mIdentity + " -> "
+ + mLocationRequest;
+ } else {
+ return mProvider + " provider " + "-registration " + mIdentity;
+ }
+ }
+ }
+
+ private static final class ProviderUpdateEvent extends ProviderEvent {
+
+ private final ProviderRequest mRequest;
+
+ private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) {
+ super(timeDelta, provider);
+ mRequest = request;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider request = " + mRequest;
+ }
+ }
+
+ private static final class ProviderReceiveLocationEvent extends ProviderEvent {
+
+ private final int mNumLocations;
+
+ private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) {
+ super(timeDelta, provider);
+ mNumLocations = numLocations;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider received location[" + mNumLocations + "]";
+ }
+ }
+
+ private static final class ProviderDeliverLocationEvent extends ProviderEvent {
+
+ private final int mNumLocations;
+ @Nullable private final CallerIdentity mIdentity;
+
+ private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations,
+ @Nullable CallerIdentity identity) {
+ super(timeDelta, provider);
+ mNumLocations = numLocations;
+ mIdentity = identity;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider delivered location[" + mNumLocations + "] to "
+ + mIdentity;
+ }
+ }
+
+ private static final class LocationPowerSaveModeEvent extends LogEvent {
+
+ @LocationPowerSaveMode
+ private final int mLocationPowerSaveMode;
+
+ private LocationPowerSaveModeEvent(long timeDelta,
+ @LocationPowerSaveMode int locationPowerSaveMode) {
+ super(timeDelta);
+ mLocationPowerSaveMode = locationPowerSaveMode;
+ }
+
+ @Override
+ public String getLogString() {
+ String mode;
+ switch (mLocationPowerSaveMode) {
+ case LOCATION_MODE_NO_CHANGE:
+ mode = "NO_CHANGE";
+ break;
+ case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+ mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
+ break;
+ case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+ mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
+ break;
+ case LOCATION_MODE_FOREGROUND_ONLY:
+ mode = "FOREGROUND_ONLY";
+ break;
+ case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+ mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
+ break;
+ default:
+ mode = "UNKNOWN";
+ break;
+ }
+ return "location power save mode changed to " + mode;
+ }
+ }
+
+ private static final class LocationEnabledEvent extends LogEvent {
+
+ private final int mUserId;
+ private final boolean mEnabled;
+
+ private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) {
+ super(timeDelta);
+ mUserId = userId;
+ mEnabled = enabled;
+ }
+
+ @Override
+ public String getLogString() {
+ return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+ }
+ }
+
+ /**
+ * Aggregate statistics for a single package under a single provider.
+ */
+ public static final class AggregateStats {
+
+ @GuardedBy("this")
+ private int mAddedRequestCount;
+ @GuardedBy("this")
+ private int mActiveRequestCount;
+ @GuardedBy("this")
+ private int mForegroundRequestCount;
+ @GuardedBy("this")
+ private int mDeliveredLocationCount;
+
+ @GuardedBy("this")
+ private long mFastestIntervalMs = Long.MAX_VALUE;
+ @GuardedBy("this")
+ private long mSlowestIntervalMs = 0;
+
+ @GuardedBy("this")
+ private long mAddedTimeTotalMs;
+ @GuardedBy("this")
+ private long mAddedTimeLastUpdateRealtimeMs;
+
+ @GuardedBy("this")
+ private long mActiveTimeTotalMs;
+ @GuardedBy("this")
+ private long mActiveTimeLastUpdateRealtimeMs;
+
+ @GuardedBy("this")
+ private long mForegroundTimeTotalMs;
+ @GuardedBy("this")
+ private long mForegroundTimeLastUpdateRealtimeMs;
+
+ AggregateStats() {}
+
+ synchronized void markRequestAdded(long intervalMillis) {
+ if (mAddedRequestCount++ == 0) {
+ mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+ }
+
+ mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs);
+ mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs);
+ }
+
+ synchronized void markRequestRemoved() {
+ updateTotals();
+ --mAddedRequestCount;
+ Preconditions.checkState(mAddedRequestCount >= 0);
+
+ mActiveRequestCount = min(mAddedRequestCount, mActiveRequestCount);
+ mForegroundRequestCount = min(mAddedRequestCount, mForegroundRequestCount);
+ }
+
+ synchronized void markRequestActive() {
+ Preconditions.checkState(mAddedRequestCount > 0);
+ if (mActiveRequestCount++ == 0) {
+ mActiveTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+ }
+ }
+
+ synchronized void markRequestInactive() {
+ updateTotals();
+ --mActiveRequestCount;
+ Preconditions.checkState(mActiveRequestCount >= 0);
+ }
+
+ synchronized void markRequestForeground() {
+ Preconditions.checkState(mAddedRequestCount > 0);
+ if (mForegroundRequestCount++ == 0) {
+ mForegroundTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+ }
+ }
+
+ synchronized void markRequestBackground() {
+ updateTotals();
+ --mForegroundRequestCount;
+ Preconditions.checkState(mForegroundRequestCount >= 0);
+ }
+
+ synchronized void markLocationDelivered() {
+ mDeliveredLocationCount++;
+ }
+
+ public synchronized void updateTotals() {
+ if (mAddedRequestCount > 0) {
+ long realtimeMs = SystemClock.elapsedRealtime();
+ mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs;
+ mAddedTimeLastUpdateRealtimeMs = realtimeMs;
+ }
+ if (mActiveRequestCount > 0) {
+ long realtimeMs = SystemClock.elapsedRealtime();
+ mActiveTimeTotalMs += realtimeMs - mActiveTimeLastUpdateRealtimeMs;
+ mActiveTimeLastUpdateRealtimeMs = realtimeMs;
+ }
+ if (mForegroundRequestCount > 0) {
+ long realtimeMs = SystemClock.elapsedRealtime();
+ mForegroundTimeTotalMs += realtimeMs - mForegroundTimeLastUpdateRealtimeMs;
+ mForegroundTimeLastUpdateRealtimeMs = realtimeMs;
+ }
+ }
+
+ @Override
+ public synchronized String toString() {
+ return "min/max interval = " + intervalToString(mFastestIntervalMs) + "/"
+ + intervalToString(mSlowestIntervalMs)
+ + ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs)
+ + "/" + formatDuration(mActiveTimeTotalMs) + "/"
+ + formatDuration(mForegroundTimeTotalMs) + ", locations = "
+ + mDeliveredLocationCount;
+ }
+
+ private static String intervalToString(long intervalMs) {
+ if (intervalMs == LocationRequest.PASSIVE_INTERVAL) {
+ return "passive";
+ } else {
+ return MILLISECONDS.toSeconds(intervalMs) + "s";
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index 03938b2..0e157c2 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -56,7 +56,4 @@
/** Returns a LocationUsageLogger. */
LocationUsageLogger getLocationUsageLogger();
-
- /** Returns a LocationEventLog. */
- LocationEventLog getLocationEventLog();
}
diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/injector/LocationEventLog.java
deleted file mode 100644
index 8d73518..0000000
--- a/services/core/java/com/android/server/location/injector/LocationEventLog.java
+++ /dev/null
@@ -1,324 +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.location.injector;
-
-import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
-import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
-import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
-import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
-import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
-
-import static com.android.server.location.LocationManagerService.D;
-
-import android.annotation.Nullable;
-import android.location.LocationRequest;
-import android.location.provider.ProviderRequest;
-import android.location.util.identity.CallerIdentity;
-import android.os.Build;
-import android.os.PowerManager.LocationPowerSaveMode;
-
-import com.android.server.location.eventlog.LocalEventLog;
-
-/** In memory event log for location events. */
-public class LocationEventLog extends LocalEventLog {
-
- private static int getLogSize() {
- if (Build.IS_DEBUGGABLE || D) {
- return 500;
- } else {
- return 200;
- }
- }
-
- private static final int EVENT_LOCATION_ENABLED = 1;
- private static final int EVENT_PROVIDER_ENABLED = 2;
- private static final int EVENT_PROVIDER_MOCKED = 3;
- private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
- private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
- private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
- private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
- private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
- private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
-
- public LocationEventLog() {
- super(getLogSize());
- }
-
- /** Logs a location enabled/disabled event. */
- public void logLocationEnabled(int userId, boolean enabled) {
- addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
- }
-
- /** Logs a location provider enabled/disabled event. */
- public void logProviderEnabled(String provider, int userId, boolean enabled) {
- addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
- }
-
- /** Logs a location provider being replaced/unreplaced by a mock provider. */
- public void logProviderMocked(String provider, boolean mocked) {
- addLogEvent(EVENT_PROVIDER_MOCKED, provider, mocked);
- }
-
- /** Logs a new client registration for a location provider. */
- public void logProviderClientRegistered(String provider, CallerIdentity identity,
- LocationRequest request) {
- addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
- }
-
- /** Logs a client unregistration for a location provider. */
- public void logProviderClientUnregistered(String provider,
- CallerIdentity identity) {
- addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
- }
-
- /** Logs a change to the provider request for a location provider. */
- public void logProviderUpdateRequest(String provider, ProviderRequest request) {
- addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
- }
-
- /** Logs a new incoming location for a location provider. */
- public void logProviderReceivedLocations(String provider, int numLocations) {
- if (Build.IS_DEBUGGABLE || D) {
- addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider, numLocations);
- }
- }
-
- /** Logs a location deliver for a client of a location provider. */
- public void logProviderDeliveredLocations(String provider, int numLocations,
- CallerIdentity identity) {
- if (Build.IS_DEBUGGABLE || D) {
- addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
- }
- }
-
- /** Logs that the location power save mode has changed. */
- public void logLocationPowerSaveMode(
- @LocationPowerSaveMode int locationPowerSaveMode) {
- addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, locationPowerSaveMode);
- }
-
- @Override
- protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
- switch (event) {
- case EVENT_LOCATION_ENABLED:
- return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
- case EVENT_PROVIDER_ENABLED:
- return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
- (Boolean) args[2]);
- case EVENT_PROVIDER_MOCKED:
- return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
- case EVENT_PROVIDER_REGISTER_CLIENT:
- return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
- (CallerIdentity) args[1], (LocationRequest) args[2]);
- case EVENT_PROVIDER_UNREGISTER_CLIENT:
- return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
- (CallerIdentity) args[1], null);
- case EVENT_PROVIDER_UPDATE_REQUEST:
- return new ProviderUpdateEvent(timeDelta, (String) args[0],
- (ProviderRequest) args[1]);
- case EVENT_PROVIDER_RECEIVE_LOCATION:
- return new ProviderReceiveLocationEvent(timeDelta, (String) args[0],
- (Integer) args[1]);
- case EVENT_PROVIDER_DELIVER_LOCATION:
- return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
- (Integer) args[1], (CallerIdentity) args[2]);
- case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
- return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
- default:
- throw new AssertionError();
- }
- }
-
- private static class ProviderEnabledEvent extends LogEvent {
-
- private final String mProvider;
- private final int mUserId;
- private final boolean mEnabled;
-
- protected ProviderEnabledEvent(long timeDelta, String provider, int userId,
- boolean enabled) {
- super(timeDelta);
- mProvider = provider;
- mUserId = userId;
- mEnabled = enabled;
- }
-
- @Override
- public String getLogString() {
- return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
- : "disabled");
- }
- }
-
- private static class ProviderMockedEvent extends LogEvent {
-
- private final String mProvider;
- private final boolean mMocked;
-
- protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) {
- super(timeDelta);
- mProvider = provider;
- mMocked = mocked;
- }
-
- @Override
- public String getLogString() {
- if (mMocked) {
- return mProvider + " provider added mock provider override";
- } else {
- return mProvider + " provider removed mock provider override";
- }
- }
- }
-
- private static class ProviderRegisterEvent extends LogEvent {
-
- private final String mProvider;
- private final boolean mRegistered;
- private final CallerIdentity mIdentity;
- @Nullable private final LocationRequest mLocationRequest;
-
- private ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
- CallerIdentity identity, @Nullable LocationRequest locationRequest) {
- super(timeDelta);
- mProvider = provider;
- mRegistered = registered;
- mIdentity = identity;
- mLocationRequest = locationRequest;
- }
-
- @Override
- public String getLogString() {
- if (mRegistered) {
- return mProvider + " provider " + "+registration " + mIdentity + " -> "
- + mLocationRequest;
- } else {
- return mProvider + " provider " + "-registration " + mIdentity;
- }
- }
- }
-
- private static class ProviderUpdateEvent extends LogEvent {
-
- private final String mProvider;
- private final ProviderRequest mRequest;
-
- private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) {
- super(timeDelta);
- mProvider = provider;
- mRequest = request;
- }
-
- @Override
- public String getLogString() {
- return mProvider + " provider request = " + mRequest;
- }
- }
-
- private static class ProviderReceiveLocationEvent extends LogEvent {
-
- private final String mProvider;
- private final int mNumLocations;
-
- private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) {
- super(timeDelta);
- mProvider = provider;
- mNumLocations = numLocations;
- }
-
- @Override
- public String getLogString() {
- return mProvider + " provider received location[" + mNumLocations + "]";
- }
- }
-
- private static class ProviderDeliverLocationEvent extends LogEvent {
-
- private final String mProvider;
- private final int mNumLocations;
- @Nullable private final CallerIdentity mIdentity;
-
- private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations,
- @Nullable CallerIdentity identity) {
- super(timeDelta);
- mProvider = provider;
- mNumLocations = numLocations;
- mIdentity = identity;
- }
-
- @Override
- public String getLogString() {
- return mProvider + " provider delivered location[" + mNumLocations + "] to "
- + mIdentity;
- }
- }
-
- private static class LocationPowerSaveModeEvent extends LogEvent {
-
- @LocationPowerSaveMode
- private final int mLocationPowerSaveMode;
-
- private LocationPowerSaveModeEvent(long timeDelta,
- @LocationPowerSaveMode int locationPowerSaveMode) {
- super(timeDelta);
- mLocationPowerSaveMode = locationPowerSaveMode;
- }
-
- @Override
- public String getLogString() {
- String mode;
- switch (mLocationPowerSaveMode) {
- case LOCATION_MODE_NO_CHANGE:
- mode = "NO_CHANGE";
- break;
- case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
- mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
- break;
- case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
- mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
- break;
- case LOCATION_MODE_FOREGROUND_ONLY:
- mode = "FOREGROUND_ONLY";
- break;
- case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
- mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
- break;
- default:
- mode = "UNKNOWN";
- break;
- }
- return "location power save mode changed to " + mode;
- }
- }
-
- private static class LocationEnabledEvent extends LogEvent {
-
- private final int mUserId;
- private final boolean mEnabled;
-
- private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) {
- super(timeDelta);
- mUserId = userId;
- mEnabled = enabled;
- }
-
- @Override
- public String getLogString() {
- return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
- }
- }
-}
diff --git a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
index 532826a..cc00d56 100644
--- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
@@ -24,6 +24,8 @@
import android.os.PowerManager.LocationPowerSaveMode;
import android.util.Log;
+import com.android.server.location.eventlog.LocationEventLog;
+
import java.util.concurrent.CopyOnWriteArrayList;
/**
diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
index 1b74865..c47a64d 100644
--- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
@@ -25,6 +25,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
import java.util.Objects;
import java.util.function.Consumer;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 48a012e..388b5a4 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -89,6 +89,7 @@
import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
+import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.fudger.LocationFudger;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
@@ -96,7 +97,6 @@
import com.android.server.location.injector.AppOpsHelper;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationAttributionHelper;
-import com.android.server.location.injector.LocationEventLog;
import com.android.server.location.injector.LocationPermissionsHelper;
import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener;
import com.android.server.location.injector.LocationPowerSaveModeHelper;
@@ -323,7 +323,7 @@
+ getRequest());
}
- mLocationEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+ mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -333,6 +333,10 @@
mIsUsingHighPower = isUsingHighPower();
onProviderListenerRegister();
+
+ if (mForeground) {
+ mEventLog.logProviderClientForeground(mName, getIdentity());
+ }
}
@GuardedBy("mLock")
@@ -344,7 +348,7 @@
onProviderListenerUnregister();
- mLocationEventLog.logProviderClientUnregistered(mName, getIdentity());
+ mEventLog.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
@@ -369,6 +373,8 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
+ mEventLog.logProviderClientActive(mName, getIdentity());
+
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
@@ -389,6 +395,8 @@
}
onProviderListenerInactive();
+
+ mEventLog.logProviderClientInactive(mName, getIdentity());
}
/**
@@ -524,6 +532,12 @@
mForeground = foreground;
+ if (mForeground) {
+ mEventLog.logProviderClientForeground(mName, getIdentity());
+ } else {
+ mEventLog.logProviderClientBackground(mName, getIdentity());
+ }
+
// note that onProviderLocationRequestChanged() is always called
return onProviderLocationRequestChanged()
|| mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
@@ -855,7 +869,7 @@
listener.deliverOnLocationChanged(deliverLocationResult,
mUseWakeLock ? mWakeLock::release : null);
- mLocationEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
+ mEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
getIdentity());
}
@@ -1154,7 +1168,7 @@
// we currently don't hold a wakelock for getCurrentLocation deliveries
listener.deliverOnLocationChanged(deliverLocationResult, null);
- mLocationEventLog.logProviderDeliveredLocations(mName,
+ mEventLog.logProviderDeliveredLocations(mName,
locationResult != null ? locationResult.size() : 0, getIdentity());
}
@@ -1223,6 +1237,7 @@
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
+ protected final LocationEventLog mEventLog;
protected final LocationManagerInternal mLocationManagerInternal;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserHelper;
@@ -1235,7 +1250,6 @@
protected final LocationAttributionHelper mLocationAttributionHelper;
protected final LocationUsageLogger mLocationUsageLogger;
protected final LocationFudger mLocationFudger;
- protected final LocationEventLog mLocationEventLog;
private final UserListener mUserChangedListener = this::onUserChanged;
private final UserSettingChangedListener mLocationEnabledChangedListener =
@@ -1273,8 +1287,8 @@
@GuardedBy("mLock")
private @Nullable OnAlarmListener mDelayedRegister;
- public LocationProviderManager(Context context, Injector injector, String name,
- @Nullable PassiveLocationProviderManager passiveManager) {
+ public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog,
+ String name, @Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
mName = Objects.requireNonNull(name);
mPassiveManager = passiveManager;
@@ -1285,6 +1299,7 @@
mEnabledListeners = new ArrayList<>();
mProviderRequestListeners = new CopyOnWriteArrayList<>();
+ mEventLog = eventLog;
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mSettingsHelper = injector.getSettingsHelper();
@@ -1297,7 +1312,6 @@
mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
mLocationAttributionHelper = injector.getLocationAttributionHelper();
mLocationUsageLogger = injector.getLocationUsageLogger();
- mLocationEventLog = injector.getLocationEventLog();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
mProvider = new MockableLocationProvider(mLock);
@@ -1437,7 +1451,7 @@
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
- mLocationEventLog.logProviderMocked(mName, provider != null);
+ mEventLog.logProviderMocked(mName, provider != null);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1925,7 +1939,7 @@
@GuardedBy("mLock")
private void setProviderRequest(ProviderRequest request) {
- mLocationEventLog.logProviderUpdateRequest(mName, request);
+ mEventLog.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
FgThread.getHandler().post(() -> {
@@ -2261,7 +2275,7 @@
}
// don't log location received for passive provider because it's spammy
- mLocationEventLog.logProviderReceivedLocations(mName, filtered.size());
+ mEventLog.logProviderReceivedLocations(mName, filtered.size());
} else {
// passive provider should get already filtered results as input
filtered = locationResult;
@@ -2361,7 +2375,7 @@
if (D) {
Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
}
- mLocationEventLog.logProviderEnabled(mName, userId, enabled);
+ mEventLog.logProviderEnabled(mName, userId, enabled);
}
// clear last locations if we become disabled
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index b35af4f..027f4e9 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -24,6 +24,7 @@
import android.os.Binder;
import com.android.internal.util.Preconditions;
+import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.Injector;
import java.util.Collection;
@@ -33,8 +34,9 @@
*/
public class PassiveLocationProviderManager extends LocationProviderManager {
- public PassiveLocationProviderManager(Context context, Injector injector) {
- super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
+ public PassiveLocationProviderManager(Context context, Injector injector,
+ LocationEventLog eventLog) {
+ super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null);
}
@Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 28c90e9..cb9793f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2372,10 +2372,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/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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4c3dfbf..5703ffe 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;
@@ -284,7 +286,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 +359,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 +3057,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 +7393,7 @@
}
@GuardedBy("mToastQueue")
- void showNextToastLocked() {
+ void showNextToastLocked(boolean lastToastWasTextRecord) {
if (mIsCurrentToastShown) {
return; // Don't show the same toast twice.
}
@@ -7406,7 +7407,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 +7478,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 +7492,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 +7502,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);
}
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..b7b72d1 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(" 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(" other overlays in the same category.");
+ 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[:NAME]");
+ out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME and");
+ out.println(" disable all other overlays for its target package. If the --category");
+ out.println(" option is given, only disables 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 {
@@ -234,12 +325,27 @@
return 1;
}
}
- final String overlay = getNextArgRequired();
- if (inCategory) {
- return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
- } else {
- return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
+
+ final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
+ final OverlayInfo overlayInfo = mInterface.getOverlayInfoByIdentifier(overlay, userId);
+ if (overlayInfo == null) {
+ err.println("Error: Unable to get overlay info of: " + overlay);
+ return 1;
}
+
+ final List<OverlayInfo> overlaysForTarget =
+ mInterface.getOverlayInfosForTarget(overlayInfo.targetPackageName, userId);
+ final OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
+ for (final OverlayInfo disableOverlay : overlaysForTarget) {
+ if ((inCategory && !Objects.equals(disableOverlay.category,overlayInfo.category))
+ || !disableOverlay.isMutable) {
+ continue;
+ }
+ builder.setEnabled(disableOverlay.getOverlayIdentifier(), false, userId);
+ }
+ builder.setEnabled(overlayInfo.getOverlayIdentifier(), true, userId);
+ mInterface.commit(builder.build());
+ return 0;
}
private int runSetPriority() 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/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/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 15e1d52..7bf7042 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -59,6 +59,7 @@
import com.android.server.utils.Snappable;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;
+import com.android.server.utils.Watched;
import com.android.server.utils.WatchedSparseArray;
import com.android.server.utils.WatchedSparseBooleanArray;
import com.android.server.utils.Watcher;
@@ -123,6 +124,7 @@
private final CookiePersistence mCookiePersistence;
/** State for uninstalled instant apps */
+ @Watched
@GuardedBy("mService.mLock")
private final WatchedSparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
@@ -132,10 +134,12 @@
* The value is a set of instant app UIDs.
* UserID -> TargetAppId -> InstantAppId
*/
+ @Watched
@GuardedBy("mService.mLock")
private final WatchedSparseArray<WatchedSparseArray<WatchedSparseBooleanArray>> mInstantGrants;
/** The set of all installed instant apps. UserID -> AppID */
+ @Watched
@GuardedBy("mService.mLock")
private final WatchedSparseArray<WatchedSparseBooleanArray> mInstalledInstantAppUids;
@@ -189,6 +193,7 @@
mUninstalledInstantApps.registerObserver(mObserver);
mInstantGrants.registerObserver(mObserver);
mInstalledInstantAppUids.registerObserver(mObserver);
+ Watchable.verifyWatchedAttributes(this, mObserver);
}
/**
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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5918652..7bf66c5 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;
@@ -1749,6 +1750,11 @@
public AndroidPackage getPackage(@NonNull String packageName) {
return getPackageLocked(packageName);
}
+
+ @Override
+ public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+ return mPmInternal.filterAppAccess(packageName, callingUid, userId);
+ }
}
/**
@@ -5940,6 +5946,21 @@
}
}
+ // Link watchables to the class
+ private void registerObserver() {
+ mPackages.registerObserver(mWatcher);
+ mSharedLibraries.registerObserver(mWatcher);
+ mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
+ mInstrumentation.registerObserver(mWatcher);
+ mWebInstantAppsDisabled.registerObserver(mWatcher);
+ mAppsFilter.registerObserver(mWatcher);
+ mInstantAppRegistry.registerObserver(mWatcher);
+ mSettings.registerObserver(mWatcher);
+ // If neither "build" attribute is true then this may be a mockito test, and verification
+ // can fail as a false positive.
+ Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild));
+ }
+
/**
* A extremely minimal constructor designed to start up a PackageManagerService instance for
* testing.
@@ -6023,15 +6044,7 @@
sSnapshotCorked = true;
mLiveComputer = createLiveComputer();
mSnapshotComputer = mLiveComputer;
-
- // Link up the watchers
- mPackages.registerObserver(mWatcher);
- mSharedLibraries.registerObserver(mWatcher);
- mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
- mInstrumentation.registerObserver(mWatcher);
- mWebInstantAppsDisabled.registerObserver(mWatcher);
- mAppsFilter.registerObserver(mWatcher);
- Watchable.verifyWatchedAttributes(this, mWatcher);
+ registerObserver();
mPackages.putAll(testParams.packages);
mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6185,15 +6198,6 @@
mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
mDomainVerificationManager.setConnection(mDomainVerificationConnection);
- // Link up the watchers
- mPackages.registerObserver(mWatcher);
- mSharedLibraries.registerObserver(mWatcher);
- mStaticLibsByDeclaringPackage.registerObserver(mWatcher);
- mInstrumentation.registerObserver(mWatcher);
- mWebInstantAppsDisabled.registerObserver(mWatcher);
- mAppsFilter.registerObserver(mWatcher);
- Watchable.verifyWatchedAttributes(this, mWatcher);
-
// Create the computer as soon as the state objects have been installed. The
// cached computer is the same as the live computer until the end of the
// constructor, at which time the invalidation method updates it. The cache is
@@ -6202,6 +6206,7 @@
sSnapshotCorked = true;
mLiveComputer = createLiveComputer();
mSnapshotComputer = mLiveComputer;
+ registerObserver();
// CHECKSTYLE:OFF IndentationCheck
synchronized (mInstallLock) {
@@ -11633,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;
}
@@ -11653,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 =
@@ -16158,8 +16160,7 @@
@Deprecated
@Override
public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
- mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
- return true;
+ return mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
}
@Deprecated
@@ -25946,6 +25947,13 @@
public String getModuleMetadataPackageName() throws RemoteException {
return PackageManagerService.this.mModuleInfoProvider.getPackageName();
}
+
+ @Override
+ public boolean hasSha256SigningCertificate(String packageName, byte[] certificate)
+ throws RemoteException {
+ return PackageManagerService.this.hasSigningCertificate(
+ packageName, certificate, CERT_INPUT_SHA256);
+ }
}
private AndroidPackage getPackage(String packageName) {
@@ -27026,6 +27034,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,
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/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 311d662..a8a6bce 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -107,9 +107,6 @@
import com.android.server.LocalServices;
import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
-import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
-import com.android.server.pm.verify.domain.DomainVerificationPersistence;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -117,6 +114,9 @@
import com.android.server.pm.permission.LegacyPermissionSettings;
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationPersistence;
import com.android.server.utils.Snappable;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.Watchable;
@@ -489,7 +489,7 @@
// App-link priority tracking, per-user
@NonNull
@Watched
- final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
+ private final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
final StringBuilder mReadMessages = new StringBuilder();
@@ -554,6 +554,7 @@
mAppIds.registerObserver(mObserver);
mOtherAppIds.registerObserver(mObserver);
mRenamedPackages.registerObserver(mObserver);
+ mNextAppLinkGeneration.registerObserver(mObserver);
mDefaultBrowserApp.registerObserver(mObserver);
Watchable.verifyWatchedAttributes(this, mObserver);
@@ -604,6 +605,7 @@
mAppIds.registerObserver(mObserver);
mOtherAppIds.registerObserver(mObserver);
mRenamedPackages.registerObserver(mObserver);
+ mNextAppLinkGeneration.registerObserver(mObserver);
mDefaultBrowserApp.registerObserver(mObserver);
Watchable.verifyWatchedAttributes(this, mObserver);
@@ -651,6 +653,7 @@
mPastSignatures.addAll(r.mPastSignatures);
mKeySetRefs.putAll(r.mKeySetRefs);
mRenamedPackages.snapshot(r.mRenamedPackages);
+ mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
// mReadMessages
mPendingPackages.addAll(r.mPendingPackages);
@@ -2709,7 +2712,6 @@
writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
writeKeySetAliasesLPr(serializer, pkg.keySetData);
- mDomainVerificationManager.writeLegacySettings(serializer, pkg.name);
writeMimeGroupLPr(serializer, pkg.mimeGroups);
serializer.endTag(null, "package");
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/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
new file mode 100644
index 0000000..6cdd4df
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.parsing.library;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+/**
+ * Updates a package to remove dependency on android.net.ipsec.ike library.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class AndroidNetIpSecIkeUpdater extends PackageSharedLibraryUpdater {
+
+ private static final String LIBRARY_NAME = "android.net.ipsec.ike";
+
+ @Override
+ public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+ removeLibrary(parsedPackage, LIBRARY_NAME);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
index 1405a7d..8a8a302 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
@@ -45,6 +45,9 @@
static {
final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
+ // Remove android.net.ipsec.ike library, it is added to boot classpath since Android S.
+ packageUpdaters.add(new AndroidNetIpSecIkeUpdater());
+
// Remove com.google.android.maps library.
packageUpdaters.add(new ComGoogleAndroidMapsUpdater());
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/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index c521f82..275dd053 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -18,8 +18,10 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
@@ -30,10 +32,17 @@
@NonNull
private final Context mContext;
+ @NonNull
+ private Callback mCallback;
+
public DomainVerificationEnforcer(@NonNull Context context) {
mContext = context;
}
+ public void setCallback(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
/**
* Enforced when mutating any state from shell or internally in the system process.
*/
@@ -67,6 +76,11 @@
"Caller " + callingUid
+ " is not allowed to query domain verification state");
}
+
+ mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
+ Binder.getCallingPid(), callingUid,
+ "Caller " + callingUid + " does not hold "
+ + android.Manifest.permission.QUERY_ALL_PACKAGES);
break;
}
}
@@ -84,28 +98,42 @@
isAllowed = true;
break;
default:
- // TODO(b/159952358): Remove permission check? The component package should
- // have been checked when the verifier component was first scanned in PMS.
- mContext.enforcePermission(
- android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
- Binder.getCallingPid(), callingUid,
- "Caller " + callingUid + " does not hold DOMAIN_VERIFICATION_AGENT");
+ final int callingPid = Binder.getCallingPid();
+ boolean isLegacyVerificationAgent = false;
+ if (mContext.checkPermission(
+ android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, callingPid,
+ callingUid) != PackageManager.PERMISSION_GRANTED) {
+ isLegacyVerificationAgent = mContext.checkPermission(
+ android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+ if (!isLegacyVerificationAgent) {
+ throw new SecurityException("Caller " + callingUid + " does not hold "
+ + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT);
+ }
+ }
+
+ // If the caller isn't a legacy verifier, it needs the QUERY_ALL permission
+ if (!isLegacyVerificationAgent) {
+ mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
+ callingPid, callingUid, "Caller " + callingUid + " does not hold "
+ + android.Manifest.permission.QUERY_ALL_PACKAGES);
+ }
+
isAllowed = proxy.isCallerVerifier(callingUid);
break;
}
if (!isAllowed) {
throw new SecurityException("Caller " + callingUid
- + " is not the approved domain verification agent, isVerifier = "
- + proxy.isCallerVerifier(callingUid));
+ + " is not the approved domain verification agent");
}
}
/**
* Enforced when mutating user selection state inside an exposed API method.
*/
- public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
- @UserIdInt int targetUserId) throws SecurityException {
+ public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
+ @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException {
if (callingUserId != targetUserId) {
mContext.enforcePermission(
Manifest.permission.INTERACT_ACROSS_USERS,
@@ -117,12 +145,51 @@
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit user selections");
+
+ if (packageName == null) {
+ return true;
+ }
+
+ return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
- public void callerIsLegacyUserSelector(int callingUid) {
+ public boolean callerIsLegacyUserSelector(int callingUid, @UserIdInt int callingUserId,
+ @NonNull String packageName, @UserIdInt int targetUserId) {
mContext.enforcePermission(
android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit user state");
+
+ if (callingUserId != targetUserId) {
+ if (mContext.checkPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ Binder.getCallingPid(), callingUid) != PackageManager.PERMISSION_GRANTED) {
+ // Legacy API did not enforce this, so for backwards compatibility, fail silently
+ return false;
+ }
+ }
+
+ return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+ }
+
+ public boolean callerIsLegacyUserQuerent(int callingUid, @UserIdInt int callingUserId,
+ @NonNull String packageName, @UserIdInt int targetUserId) {
+ if (callingUserId != targetUserId) {
+ // The legacy API enforces the _FULL variant, so maintain that here
+ mContext.enforcePermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit other users");
+ }
+
+ return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+ }
+
+ public interface Callback {
+ /**
+ * @return true if access to the given package should be filtered and the method failed as
+ * if the package was not installed
+ */
+ boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 0474d78..50fd6e3 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -174,8 +174,10 @@
* Set aside a legacy user selection that will be restored to a pending
* {@link DomainVerificationPkgState} once it's added through
* {@link #addPackage(PackageSetting)}.
+ *
+ * @return true if state changed successfully
*/
- void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
+ boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
/**
* Until the legacy APIs are entirely removed, returns the legacy state from the previously
@@ -184,12 +186,6 @@
int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
/**
- * Serialize a legacy setting that wasn't attached yet.
- * TODO: Does this even matter? Should consider for removal.
- */
- void writeLegacySettings(TypedXmlSerializer serializer, String name);
-
- /**
* Print the verification state and user selection state of a package.
*
* @param packageName the package whose state to change, or all packages if none is
@@ -235,7 +231,8 @@
throws IllegalArgumentException, NameNotFoundException;
- interface Connection extends Function<String, PackageSetting> {
+ interface Connection extends DomainVerificationEnforcer.Callback,
+ Function<String, PackageSetting> {
/**
* Notify that a settings change has been made and that eventually
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index e24e5bb..fa03274 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -47,12 +47,12 @@
import com.android.server.SystemService;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.PackageSetting;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
import org.xmlpull.v1.XmlPullParserException;
@@ -92,9 +92,9 @@
* immediately attached once its available.
* <p>
* Generally this should be not accessed directly. Prefer calling {@link
- * #getAndValidateAttachedLocked(UUID, Set, boolean)}.
+ * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)}.
*
- * @see #getAndValidateAttachedLocked(UUID, Set, boolean)
+ * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)
**/
@GuardedBy("mLock")
@NonNull
@@ -160,6 +160,7 @@
@Override
public void setConnection(@NonNull Connection connection) {
mConnection = connection;
+ mEnforcer.setCallback(mConnection);
}
@NonNull
@@ -285,7 +286,7 @@
mEnforcer.assertApprovedVerifier(callingUid, mProxy);
synchronized (mLock) {
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
- true /* forAutoVerify */);
+ true /* forAutoVerify */, callingUid, null /* userId */);
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
for (String domain : domains) {
Integer previousState = stateMap.get(domain);
@@ -389,8 +390,10 @@
public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
- mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
- mConnection.getCallingUserId(), userId);
+ if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), packageName, userId)) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
@@ -455,11 +458,18 @@
public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
throws InvalidDomainSetException, NameNotFoundException {
- mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
- mConnection.getCallingUserId(), userId);
synchronized (mLock) {
+ final int callingUid = mConnection.getCallingUid();
+ // Pass null for package name here and do the app visibility enforcement inside
+ // getAndValidateAttachedLocked instead, since this has to fail with the same invalid
+ // ID reason if the target app is invisible
+ if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
+ null /* packageName */, userId)) {
+ throw new InvalidDomainSetException(domainSetId, null,
+ InvalidDomainSetException.REASON_ID_INVALID);
+ }
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
- false /* forAutoVerify */);
+ false /* forAutoVerify */, callingUid, userId);
DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
if (enabled) {
userState.addHosts(domains);
@@ -556,8 +566,10 @@
@Override
public DomainVerificationUserSelection getDomainVerificationUserSelection(
@NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
- mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
- mConnection.getCallingUserId(), userId);
+ if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), packageName, userId)) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
@@ -844,23 +856,27 @@
}
@Override
- public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) {
- mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid());
+ public boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId,
+ int state) {
+ if (!mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), packageName, userId)) {
+ return false;
+ }
mLegacySettings.add(packageName, userId, state);
mConnection.scheduleWriteSettings();
+ return true;
}
@Override
public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) {
+ if (!mEnforcer.callerIsLegacyUserQuerent(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), packageName, userId)) {
+ return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
return mLegacySettings.getUserState(packageName, userId);
}
@Override
- public void writeLegacySettings(TypedXmlSerializer serializer, String name) {
-
- }
-
- @Override
public void clearPackage(@NonNull String packageName) {
synchronized (mLock) {
mAttachedPkgStates.remove(packageName);
@@ -935,10 +951,14 @@
* Validates parameters provided by an external caller. Checks that an ID is still live and that
* any provided domains are valid. Should be called at the beginning of each API that takes in a
* {@link UUID} domain set ID.
+ *
+ * @param userIdForFilter which user to filter app access to, or null if the caller has already
+ * validated package visibility
*/
@GuardedBy("mLock")
private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
- @NonNull Set<String> domains, boolean forAutoVerify)
+ @NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
+ @Nullable Integer userIdForFilter)
throws InvalidDomainSetException, NameNotFoundException {
if (domainSetId == null) {
throw new InvalidDomainSetException(null, null,
@@ -952,6 +972,13 @@
}
String pkgName = pkgState.getPackageName();
+
+ if (userIdForFilter != null
+ && mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) {
+ throw new InvalidDomainSetException(domainSetId, null,
+ InvalidDomainSetException.REASON_ID_INVALID);
+ }
+
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
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/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index 57cf986..e57d4ce 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -66,7 +66,7 @@
private static final String KEY_SERVICE_ENABLED = "service_enabled";
/** Default value in absence of {@link DeviceConfig} override. */
- private static final boolean DEFAULT_SERVICE_ENABLED = false;
+ private static final boolean DEFAULT_SERVICE_ENABLED = true;
static final int ORIENTATION_UNKNOWN =
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNKNOWN;
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 5e681c6..539b413 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -90,6 +90,7 @@
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -152,6 +153,7 @@
import com.android.internal.os.PowerProfile;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.role.RoleManagerLocal;
@@ -457,6 +459,8 @@
synchronized (mCpuTimePerUidFreqLock) {
return pullCpuTimePerUidFreqLocked(atomTag, data);
}
+ case FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER:
+ return pullCpuCyclesPerThreadGroupCluster(atomTag, data);
case FrameworkStatsLog.CPU_ACTIVE_TIME:
synchronized (mCpuActiveTimeLock) {
return pullCpuActiveTimeLocked(atomTag, data);
@@ -781,6 +785,7 @@
registerCpuTimePerUid();
registerCpuCyclesPerUidCluster();
registerCpuTimePerUidFreq();
+ registerCpuCyclesPerThreadGroupCluster();
registerCpuActiveTime();
registerCpuClusterTime();
registerWifiActivityInfo();
@@ -1510,6 +1515,7 @@
}
int pullCpuCyclesPerUidClusterLocked(int atomTag, List<StatsEvent> pulledData) {
+ // TODO(b/179485697): Remove power profile dependency.
PowerProfile powerProfile = new PowerProfile(mContext);
// Frequency index to frequency mapping.
long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
@@ -1653,6 +1659,81 @@
return StatsManager.PULL_SUCCESS;
}
+ private void registerCpuCyclesPerThreadGroupCluster() {
+ // TODO(b/173227907): Register only when supported.
+ int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
+ PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+ .setAdditiveFields(new int[] {3, 4})
+ .build();
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ metadata,
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
+
+ int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
+ // TODO(b/179485697): Remove power profile dependency.
+ PowerProfile powerProfile = new PowerProfile(mContext);
+ // Frequency index to frequency mapping.
+ long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
+ if (freqs == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ // Frequency index to cluster mapping.
+ int[] freqClusters = new int[freqs.length];
+ // Number of clusters.
+ int clusters;
+
+ // Initialize frequency mappings.
+ {
+ int cluster = 0;
+ long lastFreq = -1;
+ for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex) {
+ long currFreq = freqs[freqIndex];
+ if (currFreq <= lastFreq) {
+ cluster++;
+ }
+ freqClusters[freqIndex] = cluster;
+ lastFreq = currFreq;
+ }
+
+ clusters = cluster + 1;
+ }
+
+ SystemServiceCpuThreadTimes times = LocalServices.getService(BatteryStatsInternal.class)
+ .getSystemServiceCpuThreadTimes();
+ if (times == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
+ FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER,
+ times.threadCpuTimesUs, clusters, freqs, freqClusters);
+ addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
+ FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER_BINDER,
+ times.binderThreadCpuTimesUs, clusters, freqs, freqClusters);
+
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private static void addCpuCyclesPerThreadGroupClusterAtoms(
+ int atomTag, List<StatsEvent> pulledData, int threadGroup, long[] cpuTimesUs,
+ int clusters, long[] freqs, int[] freqClusters) {
+ long[] aggregatedCycles = new long[clusters];
+ long[] aggregatedTimesUs = new long[clusters];
+ for (int i = 0; i < cpuTimesUs.length; ++i) {
+ aggregatedCycles[freqClusters[i]] += freqs[i] * cpuTimesUs[i] / 1_000;
+ aggregatedTimesUs[freqClusters[i]] += cpuTimesUs[i];
+ }
+ for (int cluster = 0; cluster < clusters; ++cluster) {
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(
+ atomTag, threadGroup, cluster, aggregatedCycles[cluster],
+ aggregatedTimesUs[cluster] / 1_000));
+ }
+ }
+
private void registerCpuActiveTime() {
// the throttling is 3sec, handled in
// frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
index a54288f..e463ee2 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
@@ -33,9 +33,11 @@
*/
class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
- private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+ // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
+ private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1);
+ // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
- Duration.ofMinutes(1);
+ Duration.ofSeconds(20);
private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
@NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
diff --git a/services/core/java/com/android/server/utils/Watchable.java b/services/core/java/com/android/server/utils/Watchable.java
index f936693..44a7459 100644
--- a/services/core/java/com/android/server/utils/Watchable.java
+++ b/services/core/java/com/android/server/utils/Watchable.java
@@ -19,8 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
+import android.util.Log;
-import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
/**
@@ -61,40 +61,54 @@
public void dispatchChange(@Nullable Watchable what);
/**
- * Return true if the field is tagged with @Watched
- */
- private static boolean isWatched(Field f) {
- for (Annotation a : f.getDeclaredAnnotations()) {
- if (a.annotationType().equals(Watched.class)) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Verify that all @Watched {@link Watchable} attributes are being watched by this
* class. This requires reflection and only runs in engineering or user debug
* builds.
+ * @param base The object that contains watched attributes.
+ * @param observer The {@link Watcher} that should be watching these attributes.
+ * @param logOnly If true then log errors; if false then throw an RuntimeExecption on error.
*/
- static void verifyWatchedAttributes(Object base, Watcher observer) {
- if (Build.IS_ENG || Build.IS_USERDEBUG) {
- for (Field f : base.getClass().getDeclaredFields()) {
+ static void verifyWatchedAttributes(Object base, Watcher observer, boolean logOnly) {
+ if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+ return;
+ }
+ for (Field f : base.getClass().getDeclaredFields()) {
+ if (f.getAnnotation(Watched.class) != null) {
+ final String fn = base.getClass().getName() + "." + f.getName();
try {
- final boolean flagged = isWatched(f);
+ f.setAccessible(true);
final Object o = f.get(base);
- final boolean watchable = o instanceof Watchable;
- if (flagged && watchable) {
- Watchable attr = (Watchable) f.get(base);
+ if (o instanceof Watchable) {
+ Watchable attr = (Watchable) (o);
if (attr != null && !attr.isRegisteredObserver(observer)) {
- throw new RuntimeException(f.getName() + " missing an observer");
+ if (logOnly) {
+ Log.e("Watchable", fn + " missing an observer");
+ } else {
+ throw new RuntimeException("Watchable " + fn
+ + " missing an observer");
+ }
}
}
} catch (IllegalAccessException e) {
// The field is protected; ignore it. Other exceptions that may be thrown by
// Field.get() are allowed to roll up.
+ if (logOnly) {
+ Log.e("Watchable", fn + " not visible");
+ } else {
+ throw new RuntimeException("Watchable " + fn + " not visible");
+ }
}
}
}
}
+
+ /**
+ * Verify that all @Watched {@link Watchable} attributes are being watched by this
+ * class. This calls verifyWatchedAttributes() with logOnly set to false.
+ * @param base The object that contains watched attributes.
+ * @param observer The {@link Watcher} that should be watching these attributes.
+ */
+ static void verifyWatchedAttributes(Object base, Watcher observer) {
+ verifyWatchedAttributes(base, observer, false);
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f97af62..f16a646 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -788,7 +788,7 @@
final boolean sizeCompatFreeform = Settings.Global.getInt(
resolver, DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, 0) != 0;
final boolean supportsNonResizableMultiWindow = Settings.Global.getInt(
- resolver, DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) != 0;
+ resolver, DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) != 0;
// Transfer any global setting for forcing RTL layout, into a System Property
DisplayProperties.debug_force_rtl(forceRtl);
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 737f810..8fcdf2e 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -109,12 +109,13 @@
*/
void setBaseSettingsFilePath(@Nullable String path) {
AtomicFile settingsFile;
- if (path != null) {
- settingsFile = new AtomicFile(new File(path), WM_DISPLAY_COMMIT_TAG);
+ File file = path != null ? new File(path) : null;
+ if (file != null && file.exists()) {
+ settingsFile = new AtomicFile(file, WM_DISPLAY_COMMIT_TAG);
} else {
+ Slog.w(TAG, "display settings " + path + " does not exist, using vendor defaults");
settingsFile = getVendorSettingsFile();
}
-
setBaseSettingsStorage(new AtomicFileStorage(settingsFile));
}
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 ceebe95..bd93e04 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,6 +847,8 @@
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);
@@ -859,8 +861,6 @@
}
}
- // 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/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1f8daf6..8b18679 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -104,9 +104,6 @@
final boolean mCanAddInternalSystemWindow;
private final boolean mCanStartTasksFromRecents;
- // If non-system overlays from this process can be hidden by the user or app using
- // HIDE_NON_SYSTEM_OVERLAY_WINDOWS.
- final boolean mOverlaysCanBeHidden;
final boolean mCanCreateSystemApplicationOverlay;
final boolean mCanHideNonSystemOverlayWindows;
final boolean mCanAcquireSleepToken;
@@ -136,8 +133,6 @@
== PERMISSION_GRANTED;
mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
- mOverlaysCanBeHidden = !mCanAddInternalSystemWindow
- && !mService.mAtmInternal.isCallerRecents(mUid);
mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
== PERMISSION_GRANTED;
mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
@@ -253,11 +248,6 @@
}
@Override
- public void setTransparentRegion(IWindow window, Region region) {
- mService.setTransparentRegionWindow(this, window, region);
- }
-
- @Override
public void setInsets(IWindow window, int touchableInsets,
Rect contentInsets, Rect visibleInsets, Region touchableArea) {
mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
@@ -682,7 +672,7 @@
boolean changed;
- if (mOverlaysCanBeHidden && !mCanCreateSystemApplicationOverlay) {
+ if (!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay) {
// We want to track non-system apps adding alert windows so we can post an
// on-going notification for the user to control their visibility.
if (visible) {
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/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3c7bab3..c54c978 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -977,7 +977,7 @@
void updateSupportsNonResizableMultiWindow() {
ContentResolver resolver = mContext.getContentResolver();
final boolean supportsNonResizableMultiWindow = Settings.Global.getInt(resolver,
- DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) != 0;
+ DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) != 0;
mAtmService.mSupportsNonResizableMultiWindow = supportsNonResizableMultiWindow;
}
@@ -2150,23 +2150,6 @@
Slog.i(tag, s, e);
}
- void setTransparentRegionWindow(Session session, IWindow client, Region region) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- WindowState w = windowForClientLocked(session, client, false);
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE transparentRegionHint=%s: %s",
- region, w);
-
- if ((w != null) && w.mHasSurface) {
- w.mWinAnimator.setTransparentRegionHintLocked(region);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
Rect visibleInsets, Region touchableRegion) {
int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fd3d9ba..a94b0aa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3100,7 +3100,7 @@
}
void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) {
- if (!mSession.mOverlaysCanBeHidden
+ if (mSession.mCanAddInternalSystemWindow
|| (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index fe70dc1..ece256e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -678,14 +678,6 @@
}
}
- void setTransparentRegionHintLocked(final Region region) {
- if (mSurfaceController == null) {
- Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
- return;
- }
- mSurfaceController.setTransparentRegionHint(region);
- }
-
boolean setWallpaperOffset(int dx, int dy, float scale) {
if (mXOffset == dx && mYOffset == dy && Float.compare(mWallpaperScale, scale) == 0) {
return false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 82ba3c1..636f0bb 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -194,22 +194,6 @@
return true;
}
- void setTransparentRegionHint(final Region region) {
- if (mSurfaceControl == null) {
- Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
- return;
- }
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
- mService.openSurfaceTransaction();
- try {
- getGlobalTransaction().setTransparentRegionHint(mSurfaceControl, region);
- } finally {
- mService.closeSurfaceTransaction("setTransparentRegion");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- "<<< CLOSE TRANSACTION setTransparentRegion");
- }
- }
-
void setOpaque(boolean isOpaque) {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 91be056..1c4b034 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -56,10 +56,10 @@
"com_android_server_vibrator_VibratorController.cpp",
"com_android_server_VibratorManagerService.cpp",
"com_android_server_PersistentDataBlockService.cpp",
- "com_android_server_am_CachedAppOptimizer.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
"onload.cpp",
+ ":lib_cachedAppOptimizer_native",
":lib_networkStatsFactory_native",
],
@@ -193,3 +193,10 @@
"com_android_server_net_NetworkStatsFactory.cpp",
],
}
+
+filegroup {
+ name: "lib_cachedAppOptimizer_native",
+ srcs: [
+ "com_android_server_am_CachedAppOptimizer.cpp",
+ ],
+}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 31cc295..4551d49 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -278,6 +278,11 @@
return retVal;
}
+static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env,
+ jobject clazz) {
+ return env->NewStringUTF(CGROUP_FREEZE_PATH);
+}
+
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
@@ -286,7 +291,9 @@
(void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
{"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
{"getBinderFreezeInfo", "(I)I",
- (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}};
+ (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
+ {"getFreezerCheckPath", "()Ljava/lang/String;",
+ (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath}};
int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
{
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 404b0cf..07eb7bf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -15985,6 +15985,9 @@
public UserHandle createAndProvisionManagedProfile(
@NonNull ManagedProfileProvisioningParams provisioningParams,
@NonNull String callerPackage) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+ Objects.requireNonNull(callerPackage, "callerPackage is null");
+
final ComponentName admin = provisioningParams.getProfileAdminComponentName();
Objects.requireNonNull(admin, "admin is null");
@@ -15992,6 +15995,8 @@
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ provisioningParams.logParams(callerPackage);
+
UserInfo userInfo = null;
final long identity = Binder.clearCallingIdentity();
try {
@@ -16291,9 +16296,12 @@
@Override
public void provisionFullyManagedDevice(
- FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
- ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
+ @NonNull FullyManagedDeviceProvisioningParams provisioningParams,
+ @NonNull String callerPackage) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null.");
+ Objects.requireNonNull(callerPackage, "callerPackage is null.");
+ ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
Objects.requireNonNull(deviceAdmin, "admin is null.");
Objects.requireNonNull(provisioningParams.getOwnerName(), "owner name is null.");
@@ -16301,6 +16309,8 @@
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ provisioningParams.logParams(callerPackage);
+
final long identity = Binder.clearCallingIdentity();
try {
// TODO(b/178187130): This check fails silent provisioning, uncomment once silent
@@ -16318,8 +16328,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(
@@ -16327,15 +16339,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
@@ -16378,30 +16391,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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 98c3b99..97e7582 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);
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d863194..c2e0b77 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.parsing.component.ParsedActivity
@@ -25,7 +26,6 @@
import android.os.Build
import android.os.Process
import android.util.ArraySet
-import android.util.Singleton
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageSetting
@@ -46,14 +46,11 @@
import org.mockito.Mockito.anyString
import org.mockito.Mockito.eq
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.testng.Assert.assertThrows
import java.io.File
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
-private typealias Enforcer = DomainVerificationEnforcer
-
@RunWith(Parameterized::class)
class DomainVerificationEnforcerTest {
@@ -64,64 +61,27 @@
private const val VERIFIER_UID = Process.FIRST_APPLICATION_UID + 1
private const val NON_VERIFIER_UID = Process.FIRST_APPLICATION_UID + 2
- private const val TEST_PKG = "com.test"
+ private const val VISIBLE_PKG = "com.test.visible"
+ private val VISIBLE_UUID = UUID.fromString("8db01272-270d-4606-a3db-bb35228ff9a2")
+ private const val INVISIBLE_PKG = "com.test.invisible"
+ private val INVISIBLE_UUID = UUID.fromString("16dcb029-d96c-4a19-833a-4c9d72e2ebc3")
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun parameters(): Array<Any> {
+ val visiblePkg = mockPkg(VISIBLE_PKG)
+ val visiblePkgSetting = mockPkgSetting(VISIBLE_PKG, VISIBLE_UUID)
+ val invisiblePkg = mockPkg(INVISIBLE_PKG)
+ val invisiblePkgSetting = mockPkgSetting(INVISIBLE_PKG, INVISIBLE_UUID)
+
val makeEnforcer: (Context) -> DomainVerificationEnforcer = {
- DomainVerificationEnforcer(it)
- }
-
- val mockPkg = mockThrowOnUnmocked<AndroidPackage> {
- whenever(packageName) { TEST_PKG }
- whenever(targetSdkVersion) { Build.VERSION_CODES.S }
- whenever(activities) {
- listOf(
- ParsedActivity().apply {
- addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("https")
- addDataAuthority("example.com", null)
- }
- )
+ DomainVerificationEnforcer(it).apply {
+ setCallback(mockThrowOnUnmocked {
+ whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
+ whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
+ true
}
- )
- }
- }
-
- val uuid = UUID.randomUUID()
-
- // TODO: PackageSetting field encapsulation to move to whenever(name)
- val mockPkgSetting = spyThrowOnUnmocked(
- PackageSetting(
- TEST_PKG,
- TEST_PKG,
- File("/test"),
- null,
- null,
- null,
- null,
- 1,
- 0,
- 0,
- 0,
- null,
- null,
- null,
- uuid
- )
- ) {
- whenever(getPkg()) { mockPkg }
- whenever(domainSetId) { uuid }
- whenever(userState) {
- SparseArray<PackageUserState>().apply {
- this[0] = PackageUserState()
- }
+ })
}
}
@@ -129,142 +89,160 @@
{
val callingUidInt = AtomicInteger(-1)
val callingUserIdInt = AtomicInteger(-1)
- Triple(
- callingUidInt, callingUserIdInt, DomainVerificationService(
- it,
- mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
- mockThrowOnUnmocked {
- whenever(
- isChangeEnabled(
- anyLong(),
- any()
- )
- ) { true }
- }).apply {
- setConnection(mockThrowOnUnmocked {
- whenever(callingUid) { callingUidInt.get() }
- whenever(callingUserId) { callingUserIdInt.get() }
- whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting }
- whenever(getPackageLocked(TEST_PKG)) { mockPkg }
- whenever(schedule(anyInt(), any()))
- whenever(scheduleWriteSettings())
- })
+
+ val connection: DomainVerificationManagerInternal.Connection =
+ mockThrowOnUnmocked {
+ whenever(callingUid) { callingUidInt.get() }
+ whenever(callingUserId) { callingUserIdInt.get() }
+ whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
+ whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
+ whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
+ whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
+ whenever(schedule(anyInt(), any()))
+ whenever(scheduleWriteSettings())
+ whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
+ whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
+ true
+ }
}
- )
+ val service = DomainVerificationService(
+ it,
+ mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
+ mockThrowOnUnmocked {
+ whenever(
+ isChangeEnabled(
+ anyLong(),
+ any()
+ )
+ ) { true }
+ }).apply {
+ setConnection(connection)
+ }
+
+ Triple(callingUidInt, callingUserIdInt, service)
}
fun enforcer(
type: Type,
name: String,
- block: DomainVerificationEnforcer.(
- callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
- ) -> Unit
- ) = Params(
- type,
- makeEnforcer,
- name
- ) { enforcer, callingUid, callingUserId, userId, proxy ->
- enforcer.block(callingUid, callingUserId, userId, proxy)
+ block: DomainVerificationEnforcer.(Params.Input<DomainVerificationEnforcer>) -> Any?
+ ) = Params(type, makeEnforcer, name) {
+ it.target.block(it)
}
fun service(
type: Type,
name: String,
- block: DomainVerificationService.(
- callingUid: Int, callingUserId: Int, userId: Int
- ) -> Unit
- ) = Params(
- type,
- makeService,
- name
- ) { uidAndUserIdAndService, callingUid, callingUserId, userId, proxy ->
- val (callingUidInt, callingUserIdInt, service) = uidAndUserIdAndService
- callingUidInt.set(callingUid)
- callingUserIdInt.set(callingUserId)
- service.setProxy(proxy)
- service.addPackage(mockPkgSetting)
- service.block(callingUid, callingUserId, userId)
+ block: DomainVerificationService.(Params.Input<Triple<AtomicInteger, AtomicInteger, DomainVerificationService>>) -> Any?
+ ) = Params(type, makeService, name) {
+ val (callingUidInt, callingUserIdInt, service) = it.target
+ callingUidInt.set(it.callingUid)
+ callingUserIdInt.set(it.callingUserId)
+ service.proxy = it.proxy
+ service.addPackage(visiblePkgSetting)
+ service.addPackage(invisiblePkgSetting)
+ service.block(it)
}
return arrayOf(
- enforcer(Type.INTERNAL, "internal") { callingUid, _, _, _ ->
- assertInternal(callingUid)
+ enforcer(Type.INTERNAL, "internal") {
+ assertInternal(it.callingUid)
},
- enforcer(Type.QUERENT, "approvedQuerent") { callingUid, _, _, proxy ->
- assertApprovedQuerent(callingUid, proxy)
+ enforcer(Type.QUERENT, "approvedQuerent") {
+ assertApprovedQuerent(it.callingUid, it.proxy)
},
- enforcer(Type.VERIFIER, "approvedVerifier") { callingUid, _, _, proxy ->
- assertApprovedVerifier(callingUid, proxy)
+ enforcer(Type.VERIFIER, "approvedVerifier") {
+ assertApprovedVerifier(it.callingUid, it.proxy)
},
enforcer(
Type.SELECTOR,
"approvedUserSelector"
- ) { callingUid, callingUserId, userId, _ ->
- assertApprovedUserSelector(callingUid, callingUserId, userId)
+ ) {
+ assertApprovedUserSelector(
+ it.callingUid, it.callingUserId,
+ it.targetPackageName, it.userId
+ )
},
-
- service(Type.INTERNAL, "setStatusInternalPackageName") { _, _, _ ->
+ service(Type.INTERNAL, "setStatusInternalPackageName") {
setDomainVerificationStatusInternal(
- TEST_PKG,
+ it.targetPackageName,
DomainVerificationManager.STATE_SUCCESS,
ArraySet(setOf("example.com"))
)
},
- service(Type.INTERNAL, "setUserSelectionInternal") { _, _, userId ->
+ service(Type.INTERNAL, "setUserSelectionInternal") {
setDomainVerificationUserSelectionInternal(
- userId,
- TEST_PKG,
+ it.userId,
+ it.targetPackageName,
false,
ArraySet(setOf("example.com"))
)
},
- service(Type.INTERNAL, "verifyPackages") { _, _, _ ->
- verifyPackages(listOf(TEST_PKG), true)
+ service(Type.INTERNAL, "verifyPackages") {
+ verifyPackages(listOf(it.targetPackageName), true)
},
- service(Type.INTERNAL, "clearState") { _, _, _ ->
- clearDomainVerificationState(listOf(TEST_PKG))
+ service(Type.INTERNAL, "clearState") {
+ clearDomainVerificationState(listOf(it.targetPackageName))
},
- service(Type.INTERNAL, "clearUserSelections") { _, _, userId ->
- clearUserSelections(listOf(TEST_PKG), userId)
+ service(Type.INTERNAL, "clearUserSelections") {
+ clearUserSelections(listOf(it.targetPackageName), it.userId)
},
- service(Type.VERIFIER, "getPackageNames") { _, _, _ ->
+ service(Type.VERIFIER, "getPackageNames") {
validVerificationPackageNames
},
- service(Type.QUERENT, "getInfo") { _, _, _ ->
- getDomainVerificationInfo(TEST_PKG)
+ service(Type.QUERENT, "getInfo") {
+ getDomainVerificationInfo(it.targetPackageName)
},
- service(Type.VERIFIER, "setStatus") { _, _, _ ->
+ service(Type.VERIFIER, "setStatus") {
setDomainVerificationStatus(
- uuid,
+ it.targetDomainSetId,
setOf("example.com"),
DomainVerificationManager.STATE_SUCCESS
)
},
- service(Type.VERIFIER, "setStatusInternalUid") { callingUid, _, _ ->
+ service(Type.VERIFIER, "setStatusInternalUid") {
setDomainVerificationStatusInternal(
- callingUid,
- uuid,
+ it.callingUid,
+ it.targetDomainSetId,
setOf("example.com"),
DomainVerificationManager.STATE_SUCCESS
)
},
- service(Type.SELECTOR, "setLinkHandlingAllowed") { _, _, _ ->
- setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
+ service(Type.SELECTOR, "setLinkHandlingAllowed") {
+ setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true)
},
- service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { _, _, userId ->
- setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, userId)
+ service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") {
+ setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId)
},
- service(Type.SELECTOR, "getUserSelection") { _, _, _ ->
- getDomainVerificationUserSelection(TEST_PKG)
+ service(Type.SELECTOR, "getUserSelection") {
+ getDomainVerificationUserSelection(it.targetPackageName)
},
- service(Type.SELECTOR_USER, "getUserSelectionUserId") { _, _, userId ->
- getDomainVerificationUserSelection(TEST_PKG, userId)
+ service(Type.SELECTOR_USER, "getUserSelectionUserId") {
+ getDomainVerificationUserSelection(it.targetPackageName, it.userId)
},
- service(Type.SELECTOR, "setUserSelection") { _, _, _ ->
- setDomainVerificationUserSelection(uuid, setOf("example.com"), true)
+ service(Type.SELECTOR, "setUserSelection") {
+ setDomainVerificationUserSelection(
+ it.targetDomainSetId,
+ setOf("example.com"),
+ true
+ )
},
- service(Type.SELECTOR_USER, "setUserSelectionUserId") { _, _, userId ->
- setDomainVerificationUserSelection(uuid, setOf("example.com"), true, userId)
+ service(Type.SELECTOR_USER, "setUserSelectionUserId") {
+ setDomainVerificationUserSelection(
+ it.targetDomainSetId,
+ setOf("example.com"),
+ true,
+ it.userId
+ )
+ },
+ service(Type.LEGACY_SELECTOR, "setLegacyUserState") {
+ setLegacyUserState(
+ it.targetPackageName, it.userId,
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER
+ )
+ },
+ service(Type.LEGACY_QUERENT, "getLegacyUserState") {
+ getLegacyState(it.targetPackageName, it.userId)
},
)
}
@@ -273,9 +251,7 @@
val type: Type,
val construct: (context: Context) -> T,
val name: String,
- private val method: (
- T, callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
- ) -> Unit
+ private val method: (Input<T>) -> Any?
) {
override fun toString() = "${type}_$name"
@@ -284,10 +260,79 @@
callingUid: Int,
callingUserId: Int,
userId: Int,
+ targetPackageName: String,
+ targetDomainSetId: UUID,
proxy: DomainVerificationProxy
- ) {
- @Suppress("UNCHECKED_CAST")
- method(target as T, callingUid, callingUserId, userId, proxy)
+ ): Any? = method(
+ Input(
+ @Suppress("UNCHECKED_CAST")
+ target as T,
+ callingUid,
+ callingUserId,
+ userId,
+ targetPackageName,
+ targetDomainSetId,
+ proxy
+ )
+ )
+
+ data class Input<T>(
+ val target: T,
+ val callingUid: Int,
+ val callingUserId: Int,
+ val userId: Int,
+ val targetPackageName: String,
+ val targetDomainSetId: UUID,
+ val proxy: DomainVerificationProxy
+ )
+ }
+
+ fun mockPkg(packageName: String) = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(this.packageName) { packageName }
+ whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+ whenever(activities) {
+ listOf(
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataAuthority("example.com", null)
+ }
+ )
+ }
+ )
+ }
+ }
+
+ fun mockPkgSetting(packageName: String, domainSetId: UUID) = spyThrowOnUnmocked(
+ PackageSetting(
+ packageName,
+ packageName,
+ File("/test"),
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ domainSetId
+ )
+ ) {
+ whenever(getPkg()) { mockPkg(packageName) }
+ whenever(this.domainSetId) { domainSetId }
+ whenever(userState) {
+ SparseArray<PackageUserState>().apply {
+ this[0] = PackageUserState()
+ }
}
}
}
@@ -309,6 +354,8 @@
Type.VERIFIER -> approvedVerifier()
Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
+ Type.LEGACY_QUERENT -> legacyQuerent()
+ Type.LEGACY_SELECTOR -> legacyUserSelector()
}.run { /*exhaust*/ }
}
@@ -316,24 +363,30 @@
val context: Context = mockThrowOnUnmocked()
val target = params.construct(context)
- INTERNAL_UIDS.forEach { runMethod(target, it) }
- assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
- assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+ // Internal doesn't care about visibility
+ listOf(true, false).forEach { visible ->
+ INTERNAL_UIDS.forEach { runMethod(target, it, visible) }
+ assertFails { runMethod(target, VERIFIER_UID, visible) }
+ assertFails {
+ runMethod(target, NON_VERIFIER_UID, visible)
+ }
+ }
}
fun approvedQuerent() {
val allowUserSelection = AtomicBoolean(false)
+ val allowPreferredApps = AtomicBoolean(false)
+ val allowQueryAll = AtomicBoolean(false)
val context: Context = mockThrowOnUnmocked {
- whenever(
- enforcePermission(
- eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
- anyInt(), anyInt(), anyString()
- )
- ) {
- if (!allowUserSelection.get()) {
- throw SecurityException()
- }
- }
+ initPermission(
+ allowUserSelection,
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+ )
+ initPermission(
+ allowPreferredApps,
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS
+ )
+ initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
}
val target = params.construct(context)
@@ -341,27 +394,41 @@
verifyNoMoreInteractions(context)
+ assertFails { runMethod(target, VERIFIER_UID) }
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+ // Check that the verifier only needs QUERY_ALL to pass
+ allowQueryAll.set(true)
runMethod(target, VERIFIER_UID)
- assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+ allowQueryAll.set(false)
+
+ allowPreferredApps.set(true)
+
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
allowUserSelection.set(true)
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+ allowQueryAll.set(true)
+
runMethod(target, NON_VERIFIER_UID)
}
fun approvedVerifier() {
- val shouldThrow = AtomicBoolean(false)
+ val allowDomainVerificationAgent = AtomicBoolean(false)
+ val allowIntentVerificationAgent = AtomicBoolean(false)
+ val allowQueryAll = AtomicBoolean(false)
val context: Context = mockThrowOnUnmocked {
- whenever(
- enforcePermission(
- eq(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT),
- anyInt(), anyInt(), anyString()
- )
- ) {
- if (shouldThrow.get()) {
- throw SecurityException()
- }
- }
+ initPermission(
+ allowDomainVerificationAgent,
+ android.Manifest.permission.DOMAIN_VERIFICATION_AGENT
+ )
+ initPermission(
+ allowIntentVerificationAgent,
+ android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT
+ )
+ initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
}
val target = params.construct(context)
@@ -369,94 +436,241 @@
verifyNoMoreInteractions(context)
+ assertFails { runMethod(target, VERIFIER_UID) }
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+ allowDomainVerificationAgent.set(true)
+
+ assertFails { runMethod(target, VERIFIER_UID) }
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+ allowQueryAll.set(true)
+
runMethod(target, VERIFIER_UID)
- assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
- shouldThrow.set(true)
+ // Check that v1 verifiers are also allowed through
+ allowDomainVerificationAgent.set(false)
+ allowIntentVerificationAgent.set(true)
- assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
- assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+ runMethod(target, VERIFIER_UID)
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
}
fun approvedUserSelector(verifyCrossUser: Boolean) {
- val allowUserSelection = AtomicBoolean(true)
- val allowInteractAcrossUsers = AtomicBoolean(true)
+ val allowUserSelection = AtomicBoolean(false)
+ val allowInteractAcrossUsers = AtomicBoolean(false)
val context: Context = mockThrowOnUnmocked {
- whenever(
- enforcePermission(
- eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
- anyInt(), anyInt(), anyString()
- )
- ) {
- if (!allowUserSelection.get()) {
- throw SecurityException()
- }
- }
- whenever(
- enforcePermission(
- eq(android.Manifest.permission.INTERACT_ACROSS_USERS),
- anyInt(), anyInt(), anyString()
- )
- ) {
- if (!allowInteractAcrossUsers.get()) {
- throw SecurityException()
- }
- }
+ initPermission(
+ allowUserSelection,
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+ )
+ initPermission(
+ allowInteractAcrossUsers,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ )
}
val target = params.construct(context)
- fun runEachTestCaseWrapped(
- callingUserId: Int,
- targetUserId: Int,
- block: (testCase: () -> Unit) -> Unit = { it.invoke() }
- ) {
- block { runMethod(target, VERIFIER_UID, callingUserId, targetUserId) }
- block { runMethod(target, NON_VERIFIER_UID, callingUserId, targetUserId) }
+ fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+ // User selector makes no distinction by UID
+ val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+ if (throws) {
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+ } else {
+ allUids.forEach {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+
+ // User selector doesn't use QUERY_ALL, so the invisible package should always fail
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = false, callingUserId, targetUserId)
+ }
+ }
}
val callingUserId = 0
val notCallingUserId = 1
- runEachTestCaseWrapped(callingUserId, callingUserId)
+ runTestCases(callingUserId, callingUserId, throws = true)
if (verifyCrossUser) {
- runEachTestCaseWrapped(callingUserId, notCallingUserId)
+ runTestCases(callingUserId, notCallingUserId, throws = true)
}
- allowInteractAcrossUsers.set(false)
+ allowUserSelection.set(true)
- runEachTestCaseWrapped(callingUserId, callingUserId)
-
+ runTestCases(callingUserId, callingUserId, throws = false)
if (verifyCrossUser) {
- runEachTestCaseWrapped(callingUserId, notCallingUserId) {
- assertThrows(SecurityException::class.java, it)
- }
- }
-
- allowUserSelection.set(false)
-
- runEachTestCaseWrapped(callingUserId, callingUserId) {
- assertThrows(SecurityException::class.java, it)
- }
- if (verifyCrossUser) {
- runEachTestCaseWrapped(callingUserId, notCallingUserId) {
- assertThrows(SecurityException::class.java, it)
- }
+ runTestCases(callingUserId, notCallingUserId, throws = true)
}
allowInteractAcrossUsers.set(true)
- runEachTestCaseWrapped(callingUserId, callingUserId) {
- assertThrows(SecurityException::class.java, it)
- }
+ runTestCases(callingUserId, callingUserId, throws = false)
if (verifyCrossUser) {
- runEachTestCaseWrapped(callingUserId, notCallingUserId) {
- assertThrows(SecurityException::class.java, it)
+ runTestCases(callingUserId, notCallingUserId, throws = false)
+ }
+ }
+
+ private fun legacyUserSelector() {
+ val allowInteractAcrossUsers = AtomicBoolean(false)
+ val allowPreferredApps = AtomicBoolean(false)
+ val context: Context = mockThrowOnUnmocked {
+ initPermission(
+ allowInteractAcrossUsers,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ )
+ initPermission(
+ allowPreferredApps,
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS
+ )
+ }
+ val target = params.construct(context)
+
+ fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+ // Legacy makes no distinction by UID
+ val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+ if (throws) {
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+ } else {
+ allUids.forEach {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+
+ // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = false, callingUserId, targetUserId)
+ }
+ }
+ }
+
+ val callingUserId = 0
+ val notCallingUserId = 1
+
+ runTestCases(callingUserId, callingUserId, throws = true)
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+
+ allowPreferredApps.set(true)
+
+ runTestCases(callingUserId, callingUserId, throws = false)
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+
+ allowInteractAcrossUsers.set(true)
+
+ runTestCases(callingUserId, callingUserId, throws = false)
+ runTestCases(callingUserId, notCallingUserId, throws = false)
+ }
+
+ private fun legacyQuerent() {
+ val allowInteractAcrossUsers = AtomicBoolean(false)
+ val allowInteractAcrossUsersFull = AtomicBoolean(false)
+ val context: Context = mockThrowOnUnmocked {
+ initPermission(
+ allowInteractAcrossUsers,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ )
+ initPermission(
+ allowInteractAcrossUsersFull,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ )
+ }
+ val target = params.construct(context)
+
+ fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+ // Legacy makes no distinction by UID
+ val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+ if (throws) {
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+ } else {
+ allUids.forEach {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+
+ // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = false, callingUserId, targetUserId)
+ }
+ }
+ }
+
+ val callingUserId = 0
+ val notCallingUserId = 1
+
+ runTestCases(callingUserId, callingUserId, throws = false)
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+
+ // Legacy requires the _FULL permission, so this should continue to fail
+ allowInteractAcrossUsers.set(true)
+ runTestCases(callingUserId, callingUserId, throws = false)
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+
+ allowInteractAcrossUsersFull.set(true)
+ runTestCases(callingUserId, callingUserId, throws = false)
+ runTestCases(callingUserId, notCallingUserId, throws = false)
+ }
+
+ private fun Context.initPermission(boolean: AtomicBoolean, permission: String) {
+ whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) {
+ if (!boolean.get()) {
+ throw SecurityException()
+ }
+ }
+ whenever(checkPermission(eq(permission), anyInt(), anyInt())) {
+ if (boolean.get()) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
}
}
}
- private fun runMethod(target: Any, callingUid: Int, callingUserId: Int = 0, userId: Int = 0) {
- params.runMethod(target, callingUid, callingUserId, userId, proxy)
+ private fun runMethod(
+ target: Any,
+ callingUid: Int,
+ visible: Boolean = true,
+ callingUserId: Int = 0,
+ userId: Int = 0
+ ): Any? {
+ val packageName = if (visible) VISIBLE_PKG else INVISIBLE_PKG
+ val uuid = if (visible) VISIBLE_UUID else INVISIBLE_UUID
+ return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy)
+ }
+
+ private fun assertFails(block: () -> Any?) {
+ try {
+ val value = block()
+ // Some methods return false rather than throwing, so check that as well
+ if ((value as? Boolean) != false) {
+ // Can also return default value if it's a legacy call
+ if ((value as? Int)
+ != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
+ ) {
+ throw AssertionError("Expected call to return false, was $value")
+ }
+ }
+ } catch (e: SecurityException) {
+ } catch (e: PackageManager.NameNotFoundException) {
+ } catch (e: DomainVerificationManager.InvalidDomainSetException) {
+ // Any of these 3 exceptions are considered failures, which is expected
+ }
}
enum class Type {
@@ -473,6 +687,12 @@
SELECTOR,
// Holding the user setting permission, but targeting cross user
- SELECTOR_USER
+ SELECTOR_USER,
+
+ // Legacy required no permissions except when cross-user
+ LEGACY_QUERENT,
+
+ // Holding the legacy preferred apps permission
+ LEGACY_SELECTOR
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 5792e02..5629d1c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -267,5 +267,8 @@
whenever(getPackageLocked(TEST_PKG)) { mockPkg() }
whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings())
+
+ // This doesn't check for visibility; that's done in the enforcer test
+ whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
}
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 7c935d5..e7d56a0 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -11,9 +11,16 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+java_defaults {
+ name: "FrameworkMockingServicesTests-jni-defaults",
+ jni_libs: [
+ "libactivitymanagermockingservicestestjni",
+ ],
+}
android_test {
name: "FrameworksMockingServicesTests",
+ defaults: ["FrameworkMockingServicesTests-jni-defaults"],
srcs: ["src/**/*.java", "src/**/*.kt"],
@@ -72,4 +79,4 @@
libs: [
"android.test.runner",
],
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
new file mode 100644
index 0000000..928065a
--- /dev/null
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -0,0 +1,33 @@
+cc_library_shared {
+ name: "libactivitymanagermockingservicestestjni",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+
+ srcs: [
+ ":lib_cachedAppOptimizer_native",
+ "onload.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/base/libs",
+ "frameworks/native/services",
+ "system/memory/libmeminfo/include",
+ ],
+
+ shared_libs: [
+ "libandroid",
+ "libandroid_runtime",
+ "libbase",
+ "libbinder",
+ "liblog",
+ "libmeminfo",
+ "libnativehelper",
+ "libprocessgroup",
+ "libutils",
+ ],
+}
diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp
new file mode 100644
index 0000000..147cc47
--- /dev/null
+++ b/services/tests/mockingservicestests/jni/onload.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+/*
+ * this is a mini native libaray for cached app optimizer tests to run properly. It
+ * loads all the native methods necessary.
+ */
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+namespace android {
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+ register_android_server_am_CachedAppOptimizer(env);
+ return JNI_VERSION_1_4;
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index d860326..56d30cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -97,6 +97,7 @@
@Before
public void setUp() {
+ System.loadLibrary("activitymanagermockingservicestestjni");
mHandlerThread = new HandlerThread("");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
index 0311920..0597443 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
@@ -19,6 +19,8 @@
import android.os.IPowerManager;
import android.os.PowerManager.LocationPowerSaveMode;
+import com.android.server.location.eventlog.LocationEventLog;
+
/**
* Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
* change".
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
index c39a77e..6156ba9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
@@ -43,6 +43,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
import org.junit.After;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 8e5b16e..2822d5c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -16,9 +16,10 @@
package com.android.server.location.injector;
+import com.android.server.location.eventlog.LocationEventLog;
+
public class TestInjector implements Injector {
- private final LocationEventLog mLocationEventLog;
private final FakeUserInfoHelper mUserInfoHelper;
private final FakeAlarmHelper mAlarmHelper;
private final FakeAppOpsHelper mAppOpsHelper;
@@ -32,14 +33,17 @@
private final LocationUsageLogger mLocationUsageLogger;
public TestInjector() {
- mLocationEventLog = new LocationEventLog();
+ this(new LocationEventLog());
+ }
+
+ public TestInjector(LocationEventLog eventLog) {
mUserInfoHelper = new FakeUserInfoHelper();
mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
mAppForegroundHelper = new FakeAppForegroundHelper();
- mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(mLocationEventLog);
+ mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mEmergencyHelper = new FakeEmergencyHelper();
@@ -100,9 +104,4 @@
public LocationUsageLogger getLocationUsageLogger() {
return mLocationUsageLogger;
}
-
- @Override
- public LocationEventLog getLocationEventLog() {
- return mLocationEventLog;
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 54fa89a..66b037d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -83,6 +83,7 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
@@ -159,17 +160,19 @@
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
- mInjector = new TestInjector();
+ LocationEventLog eventLog = new LocationEventLog();
+
+ mInjector = new TestInjector(eventLog);
mInjector.getUserInfoHelper().startUser(OTHER_USER);
- mPassive = new PassiveLocationProviderManager(mContext, mInjector);
+ mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog);
mPassive.startManager();
mPassive.setRealProvider(new PassiveLocationProvider(mContext));
mProvider = new TestProvider(PROPERTIES, IDENTITY);
mProvider.setProviderAllowed(true);
- mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
+ mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive);
mManager.startManager();
mManager.setRealProvider(mProvider);
}
diff --git a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
index 50e7a03..58d6dae 100644
--- a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
+++ b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
@@ -34,7 +34,7 @@
assertEquals(0, FileUtils.readTextFile(file, 0, null).length());
// The constructor has the side effect of writing to file
- new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath(), "/dev/null");
+ new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath());
assertTrue(FileUtils.readTextFile(file, 0, null).length() > 0);
}
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/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
index caa46da..d039a9d 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -18,14 +18,23 @@
import static org.junit.Assert.assertEquals;
+import android.Manifest;
import android.app.GameManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.HashMap;
+import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -37,15 +46,88 @@
private static final int USER_ID_1 = 1001;
private static final int USER_ID_2 = 1002;
+ // Stolen from ConnectivityServiceTest.MockContext
+ class MockContext extends ContextWrapper {
+ private static final String TAG = "MockContext";
+
+ // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+ private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
+
+ MockContext(Context base) {
+ super(base);
+ }
+
+ /**
+ * Mock checks for the specified permission, and have them behave as per {@code granted}.
+ *
+ * <p>Passing null reverts to default behavior, which does a real permission check on the
+ * test package.
+ * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+ * {@link PackageManager#PERMISSION_DENIED}.
+ */
+ public void setPermission(String permission, Integer granted) {
+ mMockedPermissions.put(permission, granted);
+ }
+
+ private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
+ final Integer granted = mMockedPermissions.get(permission);
+ return granted != null ? granted : ifAbsent.get();
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ return checkMockedPermission(
+ permission, () -> super.checkPermission(permission, pid, uid));
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return checkMockedPermission(
+ permission, () -> super.checkCallingOrSelfPermission(permission));
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ final Integer granted = mMockedPermissions.get(permission);
+ if (granted == null) {
+ super.enforceCallingOrSelfPermission(permission, message);
+ return;
+ }
+
+ if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("[Test] permission denied: " + permission);
+ }
+ }
+ }
+
+ @Mock
+ private MockContext mMockContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockContext = new MockContext(InstrumentationRegistry.getContext());
+ }
+
+ private void mockModifyGameModeGranted() {
+ mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+ PackageManager.PERMISSION_GRANTED);
+ }
+
+ private void mockModifyGameModeDenied() {
+ mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+ PackageManager.PERMISSION_DENIED);
+ }
+
/**
* By default game mode is not supported.
*/
@Test
public void testGameModeDefaultValue() {
- GameManagerService gameManagerService =
- new GameManagerService(InstrumentationRegistry.getContext());
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
+ mockModifyGameModeGranted();
+
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1));
}
@@ -55,10 +137,11 @@
*/
@Test
public void testDefaultValueForNonexistentUser() {
- GameManagerService gameManagerService =
- new GameManagerService(InstrumentationRegistry.getContext());
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
+ mockModifyGameModeGranted();
+
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2);
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2));
@@ -69,10 +152,11 @@
*/
@Test
public void testGameMode() {
- GameManagerService gameManagerService =
- new GameManagerService(InstrumentationRegistry.getContext());
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
+ mockModifyGameModeGranted();
+
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
@@ -83,4 +167,48 @@
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
}
+
+ /**
+ * Test permission.MANAGE_GAME_MODE is checked
+ */
+ @Test
+ public void testGetGameModePermissionDenied() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ // Update the game mode so we can read back something valid.
+ mockModifyGameModeGranted();
+ gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+
+ // Deny permission.MANAGE_GAME_MODE and verify we get back GameManager.GAME_MODE_UNSUPPORTED
+ mockModifyGameModeDenied();
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+ }
+
+ /**
+ * Test permission.MANAGE_GAME_MODE is checked
+ */
+ @Test
+ public void testSetGameModePermissionDenied() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ // Update the game mode so we can read back something valid.
+ mockModifyGameModeGranted();
+ gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+
+ // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
+ mockModifyGameModeDenied();
+ gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE,
+ USER_ID_1);
+
+ mockModifyGameModeGranted();
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 8d890b9c..7a4b901 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -103,7 +103,8 @@
@Test
public void testNewAuthSession_eligibleSensorsSetToStateUnknown() throws RemoteException {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
- setupFace(1 /* id */, false /* confirmationAlwaysRequired */);
+ setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
@@ -118,7 +119,8 @@
@Test
public void testStartNewAuthSession() throws RemoteException {
- setupFace(0 /* id */, false /* confirmationAlwaysRequired */);
+ setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
setupFingerprint(1 /* id */, FingerprintSensorProperties.TYPE_REAR);
final boolean requireConfirmation = true;
@@ -181,9 +183,6 @@
final long operationId = 123;
final int userId = 10;
- final int callingUid = 100;
- final int callingPid = 1000;
- final int callingUserId = 10000;
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
@@ -220,7 +219,25 @@
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
assertEquals(BiometricSensor.STATE_AUTHENTICATING,
session.mPreAuthInfo.eligibleSensors.get(0).getSensorState());
+ }
+ @Test
+ public void testCancelAuthentication_whenStateAuthCalled_invokesCancel()
+ throws RemoteException {
+ final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
+
+ setupFace(0 /* id */, false /* confirmationAlwaysRequired */, faceAuthenticator);
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ 0 /* operationId */,
+ 0 /* userId */);
+
+ session.goToInitialState();
+ assertEquals(STATE_AUTH_CALLED, session.getState());
+ session.onCancelAuthSession(false /* force */);
+
+ verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
}
private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId,
@@ -282,14 +299,14 @@
false /* resetLockoutRequiresHardwareAuthToken */));
}
- private void setupFace(int id, boolean confirmationAlwaysRequired) throws RemoteException {
- IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
- when(faceAuthenticator.isHardwareDetected(any())).thenReturn(true);
- when(faceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ private void setupFace(int id, boolean confirmationAlwaysRequired,
+ IBiometricAuthenticator authenticator) throws RemoteException {
+ when(authenticator.isHardwareDetected(any())).thenReturn(true);
+ when(authenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
mSensors.add(new BiometricSensor(id,
TYPE_FACE /* modality */,
Authenticators.BIOMETRIC_STRONG /* strength */,
- faceAuthenticator) {
+ authenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
return confirmationAlwaysRequired;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 2d45726..7dd0734 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -27,6 +27,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
@@ -159,8 +161,8 @@
// Client 1 cleans up properly
verify(listener1).onError(eq(TEST_SENSOR_ID), anyInt(),
- eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0));
- verify(callback1).onClientFinished(eq(client1), eq(true) /* success */);
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0));
+ verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
verify(callback1, never()).onClientStarted(any());
// Client 2 was able to start
@@ -310,6 +312,37 @@
assertNull(mScheduler.getCurrentClient());
}
+ @Test
+ public void testInterruptPrecedingClients_whenExpected() {
+ final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
+ withSettings().extraInterfaces(Interruptable.class));
+
+ final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+ when(interrupter.interruptsPrecedingClients()).thenReturn(true);
+
+ mScheduler.scheduleClientMonitor(interruptableMonitor);
+ mScheduler.scheduleClientMonitor(interrupter);
+ waitForIdle();
+
+ verify((Interruptable) interruptableMonitor).cancel();
+ mScheduler.getInternalCallback().onClientFinished(interruptableMonitor, true /* success */);
+ }
+
+ @Test
+ public void testDoesNotInterruptPrecedingClients_whenNotExpected() {
+ final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
+ withSettings().extraInterfaces(Interruptable.class));
+
+ final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+ when(interrupter.interruptsPrecedingClients()).thenReturn(false);
+
+ mScheduler.scheduleClientMonitor(interruptableMonitor);
+ mScheduler.scheduleClientMonitor(interrupter);
+ waitForIdle();
+
+ verify((Interruptable) interruptableMonitor, never()).cancel();
+ }
+
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
}
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/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index da9dcb7..7cb72c4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -28,6 +29,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.media.AudioManager;
@@ -214,4 +216,75 @@
verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_UNKNOWN);
}
+
+ @Test
+ public void queryDisplayStatus_localDevice_2_0_targetDevice_1_4() throws Exception {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ mPlaybackDevice.mAddress, ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
+ ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+ mNativeWrapper.onCecMessage(response);
+ mTestLooper.dispatchAll();
+
+ verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+ }
+
+ @Test
+ public void queryDisplayStatus_localDevice_2_0_targetDevice_2_0() throws Exception {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+ .buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage reportPowerStatusBroadcast = HdmiCecMessageBuilder.buildReportPowerStatus(
+ ADDR_TV, ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_STANDBY);
+ mNativeWrapper.onCecMessage(reportPowerStatusBroadcast);
+ mTestLooper.dispatchAll();
+ mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ mPlaybackDevice.mAddress, ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+ verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+ }
+
+ @Test
+ public void queryDisplayStatus_localDevice_2_0_targetDevice_2_0_unknown() throws Exception {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+ .buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
+ mNativeWrapper.onCecMessage(reportPhysicalAddress);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage reportPowerStatusBroadcast = HdmiCecMessageBuilder.buildReportPowerStatus(
+ ADDR_TV, ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_UNKNOWN);
+ mNativeWrapper.onCecMessage(reportPowerStatusBroadcast);
+ mTestLooper.dispatchAll();
+ mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ mPlaybackDevice.mAddress, ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
+ ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+ mNativeWrapper.onCecMessage(response);
+ mTestLooper.dispatchAll();
+
+ verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index e6b56ca..9f0d982 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -67,6 +67,7 @@
private int mPlaybackLogicalAddress;
private boolean mWokenUp;
private boolean mStandby;
+ private boolean mActiveMediaSessionsPaused;
@Mock
private IPowerManager mIPowerManagerMock;
@@ -97,6 +98,11 @@
}
@Override
+ void pauseActiveMediaSessions() {
+ mActiveMediaSessionsPaused = true;
+ }
+
+ @Override
boolean isStandbyMessageReceived() {
return mStandby;
}
@@ -392,6 +398,54 @@
}
@Test
+ public void handleRoutingChange_otherDevice_ActiveSource_mediaSessionsPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ 0x5000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isTrue();
+ }
+
+ @Test
+ public void handleRoutingChange_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ 0x5000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleRoutingChange_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleRoutingChange_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
public void handleRoutingInformation_otherDevice_None() {
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -496,6 +550,52 @@
}
@Test
+ public void handleRoutingInformation_otherDevice_ActiveSource_mediaSessionsPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isTrue();
+ }
+
+ @Test
+ public void handleRoutingInformation_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleRoutingInformation_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleRoutingInformation_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
public void handleSetStreamPath() {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
@@ -831,6 +931,52 @@
}
@Test
+ public void handleActiveSource_otherDevice_ActiveSource_mediaSessionsPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isTrue();
+ }
+
+ @Test
+ public void handleActiveSource_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleActiveSource_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleActiveSource_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
public void losingActiveSource_standbyNow_verifyStandbyMessageIsSentOnNextStandby() {
// As described in b/161097846.
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
@@ -1158,6 +1304,54 @@
}
@Test
+ public void handleSetStreamPath_otherDevice_ActiveSource_mediaSessionsPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isTrue();
+ }
+
+ @Test
+ public void handleSetStreamPath_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleSetStreamPath_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
+ public void handleSetStreamPath_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mActiveMediaSessionsPaused = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+ mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+ mTestLooper.dispatchAll();
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ }
+
+ @Test
public void oneTouchPlay_SendStandbyOnSleepToTv() {
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
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/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/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/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
new file mode 100644
index 0000000..70d85b6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.parsing.library;
+
+import android.os.Build;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link AndroidNetIpSecIkeUpdater}
+ */
+@Presubmit
+@SmallTest
+@RunWith(JUnit4.class)
+public class AndroidNetIpSecIkeUpdaterTest extends PackageSharedLibraryUpdaterTest {
+
+ @Test
+ public void otherUsesLibraries() {
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.O)
+ .addUsesLibrary("other")
+ .addUsesOptionalLibrary("optional")
+ .addUsesLibrary("android.net.ipsec.ike")
+ .hideAsParsed());
+ AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.O)
+ .addUsesLibrary("other")
+ .addUsesOptionalLibrary("optional")
+ .hideAsParsed())
+ .hideAsFinal();
+ checkBackwardsCompatibility(before, after);
+ }
+
+ @Test
+ public void in_usesLibraries() {
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ .addUsesLibrary("android.net.ipsec.ike")
+ .hideAsParsed());
+
+ AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ .hideAsParsed())
+ .hideAsFinal();
+
+ checkBackwardsCompatibility(before, after);
+ }
+
+ @Test
+ public void in_usesOptionalLibraries() {
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ .addUsesOptionalLibrary("android.net.ipsec.ike")
+ .hideAsParsed());
+
+ AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ .hideAsParsed())
+ .hideAsFinal();
+
+ checkBackwardsCompatibility(before, after);
+ }
+
+ private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
+ checkBackwardsCompatibility(before, after, AndroidNetIpSecIkeUpdater::new);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
index 09c8142..9768f17 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
@@ -165,6 +165,23 @@
checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
}
+ /**
+ * Ensures that the {@link PackageBackwardCompatibility} uses a
+ * {@link AndroidNetIpSecIkeUpdater}.
+ */
+ @Test
+ public void android_net_ipsec_ike_in_usesLibraries() {
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ .addUsesLibrary("android.net.ipsec.ike")
+ .hideAsParsed());
+
+ ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+ checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
+ }
+
private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance);
}
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/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 0cb1255..9e419d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1526,15 +1526,19 @@
@Override
public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
- long endTime, String callingPackage) {
+ long endTime, String callingPackage, int userId) {
if (!hasPermission(callingPackage)) {
return null;
}
- final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
- Binder.getCallingUid(), UserHandle.getCallingUserId());
+ final int callingUid = Binder.getCallingUid();
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
+ userId, false, true, "queryUsageStats", callingPackage);
- final int userId = UserHandle.getCallingUserId();
+ // Check the caller's userId for obfuscation decision, not the user being queried
+ final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
+ callingUid, UserHandle.getCallingUserId());
+
final long token = Binder.clearCallingIdentity();
try {
final List<UsageStats> results = UsageStatsService.this.queryUsageStats(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index be36f82..f687e4b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -581,17 +581,24 @@
}
}
- VoiceInteractionServiceInfo findAvailInteractor(int userHandle, String packageName) {
- List<ResolveInfo> available =
- mContext.getPackageManager().queryIntentServicesAsUser(
- new Intent(VoiceInteractionService.SERVICE_INTERFACE)
- .setPackage(packageName),
- PackageManager.GET_META_DATA
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+ private List<ResolveInfo> queryInteractorServices(
+ @UserIdInt int user,
+ @Nullable String packageName) {
+ return mContext.getPackageManager().queryIntentServicesAsUser(
+ new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(packageName),
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ user);
+ }
+
+ VoiceInteractionServiceInfo findAvailInteractor(
+ @UserIdInt int user,
+ @Nullable String packageName) {
+ List<ResolveInfo> available = queryInteractorServices(user, packageName);
int numAvailable = available.size();
if (numAvailable == 0) {
- Slog.w(TAG, "no available voice interaction services found for user " + userHandle);
+ Slog.w(TAG, "no available voice interaction services found for user " + user);
return null;
}
// Find first system package. We never want to allow third party services to
@@ -1643,13 +1650,7 @@
String pkg = roleHolders.get(0);
// Try to set role holder as VoiceInteractionService
- List<ResolveInfo> services = mPm.queryIntentServicesAsUser(
- new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg),
- PackageManager.GET_META_DATA
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
-
- for (ResolveInfo resolveInfo : services) {
+ for (ResolveInfo resolveInfo : queryInteractorServices(userId, pkg)) {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
VoiceInteractionServiceInfo voiceInteractionServiceInfo =
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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ac584c1..21cf3e5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4748,6 +4748,21 @@
public static final String KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL =
"network_temp_not_metered_supported_bool";
+ /*
+ * Boolean indicating whether the SIM PIN can be stored and verified
+ * seamlessly after an unattended reboot.
+ *
+ * The device configuration value {@code config_allow_pin_storage_for_unattended_reboot}
+ * ultimately controls whether this carrier configuration option is used. Where
+ * {@code config_allow_pin_storage_for_unattended_reboot} is false, the value of the
+ * {@link #KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL} carrier configuration option is
+ * ignored.
+ *
+ * @hide
+ */
+ public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL =
+ "store_sim_pin_for_unattended_reboot_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -5304,6 +5319,7 @@
sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
+ sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 65b8de2..16ffd9e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11255,16 +11255,21 @@
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
- * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+ * Additionally, depending on the level of location permissions the caller holds (i.e. no
+ * location permissions, {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}, or
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}), location-sensitive fields will
+ * be cleared from the return value.
+ *
+ * <p>Note also that if the caller holds any sort of location permission, a usage event for the
+ * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION} or
+ * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION}
+ * will be logged against the caller when calling this method.
*
* May return {@code null} when the subscription is inactive or when there was an error
* communicating with the phone process.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.ACCESS_COARSE_LOCATION
- })
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public @Nullable ServiceState getServiceState() {
return getServiceStateForSubscriber(getSubId());
}
@@ -14994,6 +14999,7 @@
}
return THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR;
}
+
/**
* Registers a listener object to receive notification of changes
* in specified telephony states.
@@ -15350,4 +15356,66 @@
return PhoneCapability.DEFAULT_SSSS_CAPABILITY;
}
}
+
+ /**
+ * The unattended reboot was prepared successfully.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0;
+
+ /**
+ * The unattended reboot was prepared, but the user will need to manually
+ * enter the PIN code of at least one SIM card present in the device.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1;
+
+ /**
+ * The unattended reboot was not prepared due to generic error.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"PREPARE_UNATTENDED_REBOOT_"},
+ value = {
+ PREPARE_UNATTENDED_REBOOT_SUCCESS,
+ PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED,
+ PREPARE_UNATTENDED_REBOOT_ERROR
+ })
+ public @interface PrepareUnattendedRebootResult {}
+
+ /**
+ * Prepare TelephonyManager for an unattended reboot. The reboot is required to be done
+ * shortly (e.g. within 15 seconds) after the API is invoked.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#REBOOT}
+ *
+ * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success.
+ * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains
+ * at least one SIM card for which the user needs to manually enter the PIN
+ * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
+ * of error.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REBOOT)
+ @PrepareUnattendedRebootResult
+ public int prepareForUnattendedReboot() {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.prepareForUnattendedReboot();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Telephony#prepareForUnattendedReboot RemoteException", e);
+ e.rethrowFromSystemServer();
+ }
+ return PREPARE_UNATTENDED_REBOOT_ERROR;
+ }
}
diff --git a/tools/hiddenapi/Android.bp b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
similarity index 68%
rename from tools/hiddenapi/Android.bp
rename to telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
index e0eb06cb..0830ff2 100644
--- a/tools/hiddenapi/Android.bp
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-python_binary_host {
- name: "merge_csv",
- main: "merge_csv.py",
- srcs: ["merge_csv.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: true
- },
- },
-}
+package android.telephony.ims;
+
+parcelable ImsRegistrationAttributes;
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
new file mode 100644
index 0000000..ccb3231
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -0,0 +1,252 @@
+/*
+ * 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.telephony.ims;
+
+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 android.telephony.AccessNetworkConstants;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Contains the attributes associated with the current IMS registration.
+ */
+public final class ImsRegistrationAttributes implements Parcelable {
+
+ /**
+ * Attribute to specify if an EPDG tunnel is setup over the cellular internet APN.
+ * <p>
+ * If IMS is registered through an EPDG tunnel is setup over the cellular internet APN then this
+ * bit will be set. If IMS is registered through the IMS APN, then this bit will not be set.
+ *
+ */
+ public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1 << 0;
+
+ /** @hide */
+ // Defines the underlying radio technology type that we have registered for IMS over.
+ @IntDef(prefix = "ATTR_",
+ value = {
+ ATTR_EPDG_OVER_CELL_INTERNET,
+ },
+ flag = true)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ImsAttributeFlag {}
+
+ /**
+ * Builder for creating {@link ImsRegistrationAttributes} instances.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private final int mRegistrationTech;
+ private Set<String> mFeatureTags = Collections.emptySet();
+
+ /**
+ * Build a new instance of {@link ImsRegistrationAttributes}.
+ *
+ * @param registrationTech The Radio Access Technology that IMS is registered on.
+ */
+ public Builder(@ImsRegistrationImplBase.ImsRegistrationTech int registrationTech) {
+ mRegistrationTech = registrationTech;
+ }
+
+ /**
+ * Optional IMS feature tags included in this IMS registration.
+ * @param tags A set of Strings containing the MMTEL and RCS feature tags associated with
+ * the IMS registration. This information is used for services such as the UCE
+ * service to ascertain the complete IMS registration state to ensure the SIP
+ * PUBLISH is accurate. The format of the set of feature tags must be one feature
+ * tag key and value per entry. Each feature tag will contain the feature tag name
+ * and string value (if applicable), even if they have the same feature tag name.
+ * For example,
+ * {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg,
+ * urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} must
+ * be split into three feature tag entries:
+ * {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg",
+ * +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session",
+ * +g.gsma.callcomposer}}.
+ */
+ public @NonNull Builder setFeatureTags(@NonNull Set<String> tags) {
+ if (tags == null) {
+ throw new IllegalArgumentException("feature tag set must not be null");
+ }
+ mFeatureTags = new ArraySet<>(tags);
+ return this;
+ }
+
+ /**
+ * @return A new instance created from this builder.
+ */
+ public @NonNull ImsRegistrationAttributes build() {
+ return new ImsRegistrationAttributes(mRegistrationTech,
+ RegistrationManager.getAccessType(mRegistrationTech),
+ getAttributeFlags(mRegistrationTech),
+ mFeatureTags);
+ }
+
+ /**
+ * @return attribute flags from the registration technology.
+ */
+ private static int getAttributeFlags(int imsRadioTech) {
+ int attributes = 0;
+ if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
+ attributes |= ATTR_EPDG_OVER_CELL_INTERNET;
+ }
+ return attributes;
+ }
+ }
+
+ private final int mRegistrationTech;
+ private final int mTransportType;
+ private final int mImsAttributeFlags;
+ private final ArrayList<String> mFeatureTags;
+
+ /**
+ * Create a new {@link ImsRegistrationAttributes} instance.
+ *
+ * @param registrationTech The technology that IMS has been registered on.
+ * @param transportType The transport type that IMS has been registered on.
+ * @param imsAttributeFlags The attributes associated with the IMS registration.
+ * @param featureTags The feature tags included in the IMS registration.
+ * @see Builder
+ * @hide
+ */
+ public ImsRegistrationAttributes(
+ @ImsRegistrationImplBase.ImsRegistrationTech int registrationTech,
+ @AccessNetworkConstants.TransportType int transportType,
+ @ImsAttributeFlag int imsAttributeFlags,
+ @Nullable Set<String> featureTags) {
+ mRegistrationTech = registrationTech;
+ mTransportType = transportType;
+ mImsAttributeFlags = imsAttributeFlags;
+ mFeatureTags = new ArrayList<>(featureTags);
+ }
+
+ /**@hide*/
+ public ImsRegistrationAttributes(Parcel source) {
+ mRegistrationTech = source.readInt();
+ mTransportType = source.readInt();
+ mImsAttributeFlags = source.readInt();
+ mFeatureTags = new ArrayList<>();
+ source.readList(mFeatureTags, null /*classloader*/);
+ }
+
+ /**
+ * @return The Radio Access Technology that the IMS registration has been registered over.
+ * @hide
+ */
+ @SystemApi
+ public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTechnology() {
+ return mRegistrationTech;
+ }
+
+ /**
+ * @return The access network transport type that IMS has been registered over.
+ */
+ public @AccessNetworkConstants.TransportType int getTransportType() {
+ return mTransportType;
+ }
+
+ /**
+ * @return A bit-mask containing attributes associated with the IMS registration.
+ */
+ public @ImsAttributeFlag int getAttributeFlags() {
+ return mImsAttributeFlags;
+ }
+
+ /**
+ * Gets the Set of feature tags associated with the current IMS registration, if the IMS
+ * service supports supplying this information.
+ * <p>
+ * The format of the set of feature tags will be one feature tag key and value per entry and
+ * will potentially contain MMTEL and RCS feature tags, depending the configuration of the IMS
+ * service associated with the registration indications. Each feature tag will contain the
+ * feature tag name and string value (if applicable), even if they have the same feature tag
+ * name. For example, {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg,
+ * urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} will be split
+ * into three feature tag entries:
+ * {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg",
+ * +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session",
+ * +g.gsma.callcomposer}}.
+ * @return The Set of feature tags associated with the current IMS registration.
+ */
+ public @NonNull Set<String> getFeatureTags() {
+ if (mFeatureTags == null) {
+ return Collections.emptySet();
+ }
+ return new ArraySet<>(mFeatureTags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mRegistrationTech);
+ dest.writeInt(mTransportType);
+ dest.writeInt(mImsAttributeFlags);
+ dest.writeList(mFeatureTags);
+ }
+
+ public static final @NonNull Creator<ImsRegistrationAttributes> CREATOR =
+ new Creator<ImsRegistrationAttributes>() {
+ @Override
+ public ImsRegistrationAttributes createFromParcel(Parcel source) {
+ return new ImsRegistrationAttributes(source);
+ }
+
+ @Override
+ public ImsRegistrationAttributes[] newArray(int size) {
+ return new ImsRegistrationAttributes[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ImsRegistrationAttributes that = (ImsRegistrationAttributes) o;
+ return mRegistrationTech == that.mRegistrationTech
+ && mTransportType == that.mTransportType
+ && mImsAttributeFlags == that.mImsAttributeFlags
+ && Objects.equals(mFeatureTags, that.mFeatureTags);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRegistrationTech, mTransportType, mImsAttributeFlags, mFeatureTags);
+ }
+
+ @Override
+ public String toString() {
+ return "ImsRegistrationAttributes { transportType= " + mTransportType + ", attributeFlags="
+ + mImsAttributeFlags + ", featureTags=[" + mFeatureTags + "]}";
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index b430bef..c682afe 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -72,30 +72,6 @@
*/
int REGISTRATION_STATE_REGISTERED = 2;
- /**
- * @hide
- */
- // Defines the underlying radio technology type that we have registered for IMS over.
- @IntDef(prefix = "ATTR_",
- value = {
- ATTR_EPDG_OVER_CELL_INTERNET,
- },
- flag = true)
- @Retention(RetentionPolicy.SOURCE)
- public @interface ImsAttributes {}
-
- /**
- * Attribute to specify if EPDG tunnel is setup over cellular internet.
- * if EPDG tunnel is setup over cellular internet then this bit will be set else the same will
- * not be set.
- */
- int ATTR_EPDG_OVER_CELL_INTERNET = 0x00000001;
-
- //******************************************************************************************
- // Next attribute value: 0x00000002
- //******************************************************************************************
-
-
/**@hide*/
// Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
// and WWAN are more accurate constants.
@@ -103,7 +79,8 @@
new HashMap<Integer, Integer>() {{
// Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
// case, since it is defined.
- put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, -1);
+ put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+ AccessNetworkConstants.TRANSPORT_TYPE_INVALID);
put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
@@ -132,6 +109,20 @@
}
/**
+ * @param regtech The registration technology.
+ * @return The Access Network type from registration technology.
+ * @hide
+ */
+ static int getAccessType(int regtech) {
+ if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regtech)) {
+ Log.w("RegistrationManager", "getAccessType - invalid regType returned: "
+ + regtech);
+ return AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+ }
+ return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regtech);
+ }
+
+ /**
* Callback class for receiving IMS network Registration callback events.
* @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
* @see #unregisterImsRegistrationCallback(RegistrationCallback)
@@ -149,45 +140,24 @@
}
@Override
- public void onRegistered(int imsRadioTech) {
+ public void onRegistered(ImsRegistrationAttributes attr) {
if (mLocalCallback == null) return;
final long callingIdentity = Binder.clearCallingIdentity();
try {
- mExecutor.execute(() -> {
- mLocalCallback.onRegistered(getAccessType(imsRadioTech));
- });
- int attributes = 0;
- if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
- attributes = changeBitmask(attributes, ATTR_EPDG_OVER_CELL_INTERNET,
- true);
- }
- final int finalattributes = attributes;
- mExecutor.execute(() ->
- mLocalCallback.onRegistered(getAccessType(imsRadioTech),
- finalattributes));
+ mExecutor.execute(() -> mLocalCallback.onRegistered(attr));
} finally {
restoreCallingIdentity(callingIdentity);
}
}
@Override
- public void onRegistering(int imsRadioTech) {
+ public void onRegistering(ImsRegistrationAttributes attr) {
if (mLocalCallback == null) return;
final long callingIdentity = Binder.clearCallingIdentity();
try {
- mExecutor.execute(() ->
- mLocalCallback.onRegistering(getAccessType(imsRadioTech)));
- int attributes = 0;
- if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
- attributes = changeBitmask(attributes, ATTR_EPDG_OVER_CELL_INTERNET,
- true);
- }
- final int finalattributes = attributes;
- mExecutor.execute(() ->
- mLocalCallback.onRegistering(getAccessType(imsRadioTech),
- finalattributes));
+ mExecutor.execute(() -> mLocalCallback.onRegistering(attr));
} finally {
restoreCallingIdentity(callingIdentity);
}
@@ -232,31 +202,6 @@
private void setExecutor(Executor executor) {
mExecutor = executor;
}
-
- private static int getAccessType(int regType) {
- if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regType)) {
- Log.w("RegistrationManager", "RegistrationBinder - invalid regType returned: "
- + regType);
- return -1;
- }
- return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regType);
- }
-
- /**
- * Changes a attribute bit-mask to add or remove an attribute.
- *
- * @param bitmask The bit-mask.
- * @param bitfield The bit-field to change.
- * @param enabled Whether the bit-field should be set or removed.
- * @return The bit-mask with the bit-field changed.
- */
- private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
- if (enabled) {
- return bitmask | bitfield;
- } else {
- return bitmask & ~bitfield;
- }
- }
}
private final RegistrationBinder mBinder = new RegistrationBinder(this);
@@ -265,7 +210,7 @@
* Notifies the framework when the IMS Provider is registered to the IMS network.
*
* @param imsTransportType the radio access technology.
- * @deprecated Use {@link #onRegistered(int, int)} instead.
+ * @deprecated Use {@link #onRegistered(ImsRegistrationAttributes)} instead.
*/
@Deprecated
public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType) {
@@ -273,25 +218,20 @@
/**
* Notifies the framework when the IMS Provider is registered to the IMS network
- * with corresponding attributes
+ * with corresponding attributes.
*
- * @param imsTransportType the radio access technology.
- * @param registrationAttributes IMS registration attributes as a bitmap of attributes.
- * Possible attributes are following
- * <ul>
- * <li>{@link #ATTR_EPDG_OVER_CELL_INTERNET}</li>
- * </ul>
- *
+ * @param attributes The attributes associated with this IMS registration.
*/
- public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType,
- @ImsAttributes int registrationAttributes) {
+ public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
+ // Default impl to keep backwards compatibility with old implementations
+ onRegistered(attributes.getTransportType());
}
/**
* Notifies the framework when the IMS Provider is trying to register the IMS network.
*
* @param imsTransportType the radio access technology.
- * @deprecated Use {@link #onRegistering(int, int)} instead.
+ * @deprecated Use {@link #onRegistering(ImsRegistrationAttributes)} instead.
*/
public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType) {
}
@@ -299,12 +239,11 @@
/**
* Notifies the framework when the IMS Provider is trying to register the IMS network.
*
- * @param imsTransportType the radio access technology.
- * @param registrationAttributes IMS registration attributes as a bitmap of attributes.
- * Possible attributes are following
+ * @param attributes The attributes associated with this IMS registration.
*/
- public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType,
- @ImsAttributes int registrationAttributes) {
+ public void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
+ // Default impl to keep backwards compatibility with old implementations
+ onRegistering(attributes.getTransportType());
}
/**
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
index 749b191..179407c 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
@@ -21,6 +21,7 @@
import android.telephony.ims.stub.ImsFeatureConfiguration;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
/**
* See {@link ImsManager#RegistrationCallback} for more information.
@@ -28,8 +29,8 @@
* {@hide}
*/
oneway interface IImsRegistrationCallback {
- void onRegistered(int imsRadioTech);
- void onRegistering(int imsRadioTech);
+ void onRegistered(in ImsRegistrationAttributes attr);
+ void onRegistering(in ImsRegistrationAttributes attr);
void onDeregistered(in ImsReasonInfo info);
void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info);
void onSubscriberAssociatedUriChanged(in Uri[] uris);
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 4f753c3..39994be 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -18,17 +18,18 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.Uri;
import android.os.RemoteException;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.util.RemoteCallbackListExt;
import com.android.internal.util.ArrayUtils;
@@ -89,7 +90,10 @@
@Override
public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException {
- return getConnectionType();
+ synchronized (mLock) {
+ return (mRegistrationAttributes == null) ? REGISTRATION_TECH_NONE
+ : mRegistrationAttributes.getRegistrationTechnology();
+ }
}
@Override
@@ -122,8 +126,7 @@
new RemoteCallbackListExt<>();
private final Object mLock = new Object();
// Locked on mLock
- private @ImsRegistrationTech
- int mConnectionType = REGISTRATION_TECH_NONE;
+ private ImsRegistrationAttributes mRegistrationAttributes;
// Locked on mLock
private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
// Locked on mLock, create unspecified disconnect cause.
@@ -201,18 +204,24 @@
/**
* Notify the framework that the device is connected to the IMS network.
*
- * @param imsRadioTech the radio access technology. Valid values are defined as
- * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
- * {@link #REGISTRATION_TECH_CROSS_SIM}.
+ * @param imsRadioTech the radio access technology.
*/
public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
- updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERED);
+ onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
+ }
+
+ /**
+ * Notify the framework that the device is connected to the IMS network.
+ *
+ * @param attributes The attributes associated with the IMS registration.
+ */
+ public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
+ updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
mCallbacks.broadcastAction((c) -> {
try {
- c.onRegistered(imsRadioTech);
+ c.onRegistered(attributes);
} catch (RemoteException e) {
- Log.w(LOG_TAG, e + " " + "onRegistrationConnected() - Skipping " +
- "callback.");
+ Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback.");
}
});
}
@@ -220,18 +229,24 @@
/**
* Notify the framework that the device is trying to connect the IMS network.
*
- * @param imsRadioTech the radio access technology. Valid values are defined as
- * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
- * {@link #REGISTRATION_TECH_CROSS_SIM}.
+ * @param imsRadioTech the radio access technology.
*/
public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
- updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERING);
+ onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
+ }
+
+ /**
+ * Notify the framework that the device is trying to connect the IMS network.
+ *
+ * @param attributes The attributes associated with the IMS registration.
+ */
+ public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
+ updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
mCallbacks.broadcastAction((c) -> {
try {
- c.onRegistering(imsRadioTech);
+ c.onRegistering(attributes);
} catch (RemoteException e) {
- Log.w(LOG_TAG, e + " " + "onRegistrationProcessing() - Skipping " +
- "callback.");
+ Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback.");
}
});
}
@@ -260,8 +275,7 @@
try {
c.onDeregistered(reasonInfo);
} catch (RemoteException e) {
- Log.w(LOG_TAG, e + " " + "onRegistrationDisconnected() - Skipping " +
- "callback.");
+ Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
}
});
}
@@ -281,8 +295,7 @@
try {
c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
} catch (RemoteException e) {
- Log.w(LOG_TAG, e + " " + "onRegistrationChangeFailed() - Skipping " +
- "callback.");
+ Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback.");
}
});
}
@@ -306,14 +319,13 @@
try {
callback.onSubscriberAssociatedUriChanged(uris);
} catch (RemoteException e) {
- Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping "
- + "callback.");
+ Log.w(LOG_TAG, e + "onSubscriberAssociatedUriChanged() - Skipping callback.");
}
}
- private void updateToState(@ImsRegistrationTech int connType, int newState) {
+ private void updateToState(ImsRegistrationAttributes attributes, int newState) {
synchronized (mLock) {
- mConnectionType = connType;
+ mRegistrationAttributes = attributes;
mRegistrationState = newState;
mLastDisconnectCause = null;
}
@@ -325,7 +337,7 @@
mUrisSet = false;
mUris = null;
- updateToState(REGISTRATION_TECH_NONE,
+ updateToState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE).build(),
RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
if (info != null) {
mLastDisconnectCause = info;
@@ -337,30 +349,19 @@
}
/**
- * @return the current registration connection type. Valid values are
- * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
- * {@link #REGISTRATION_TECH_CROSS_SIM}.
- * @hide
- */
- @VisibleForTesting
- public final @ImsRegistrationTech int getConnectionType() {
- synchronized (mLock) {
- return mConnectionType;
- }
- }
-
- /**
* @param c the newly registered callback that will be updated with the current registration
* state.
*/
private void updateNewCallbackWithState(IImsRegistrationCallback c)
throws RemoteException {
int state;
+ ImsRegistrationAttributes attributes;
ImsReasonInfo disconnectInfo;
boolean urisSet;
Uri[] uris;
synchronized (mLock) {
state = mRegistrationState;
+ attributes = mRegistrationAttributes;
disconnectInfo = mLastDisconnectCause;
urisSet = mUrisSet;
uris = mUris;
@@ -371,11 +372,11 @@
break;
}
case RegistrationManager.REGISTRATION_STATE_REGISTERING: {
- c.onRegistering(getConnectionType());
+ c.onRegistering(attributes);
break;
}
case RegistrationManager.REGISTRATION_STATE_REGISTERED: {
- c.onRegistered(getConnectionType());
+ c.onRegistered(attributes);
break;
}
case REGISTRATION_STATE_UNKNOWN: {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0cd17da3..1d04953 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2387,4 +2387,18 @@
* Gets the current phone capability.
*/
PhoneCapability getPhoneCapability();
+
+ /**
+ * Prepare TelephonyManager for an unattended reboot. The reboot is
+ * required to be done shortly after the API is invoked.
+ *
+ * Requires system privileges.
+ *
+ * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success.
+ * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains
+ * at least one SIM card for which the user needs to manually enter the PIN
+ * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
+ * of error.
+ */
+ int prepareForUnattendedReboot();
}
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 39dc9c2..e2d2eca 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -16,7 +16,10 @@
name: "ApkVerityTest",
srcs: ["src/**/*.java"],
libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
- static_libs: ["frameworks-base-hostutils"],
+ static_libs: [
+ "block_device_writer_jar",
+ "frameworks-base-hostutils",
+ ],
test_suites: ["general-tests", "vts"],
target_required: [
"block_device_writer_module",
diff --git a/tests/ApkVerityTest/OWNERS b/tests/ApkVerityTest/OWNERS
new file mode 100644
index 0000000..d67285ed
--- /dev/null
+++ b/tests/ApkVerityTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+
+victorhsieh@google.com
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index 37fbc29..8f2d4bc 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -51,3 +51,9 @@
test_suites: ["general-tests", "pts", "vts"],
gtest: false,
}
+
+java_library_host {
+ name: "block_device_writer_jar",
+ srcs: ["src/**/*.java"],
+ libs: ["tradefed", "junit"],
+}
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
new file mode 100644
index 0000000..5c2c15b
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.blockdevicewriter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper for block_device_writer command.
+ *
+ * <p>To use this class, please push block_device_writer binary to /data/local/tmp.
+ * 1. In Android.bp, add:
+ * <pre>
+ * target_required: ["block_device_writer_module"],
+ * </pre>
+ * 2. In AndroidText.xml, add:
+ * <pre>
+ * <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ * <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+ * </target_preparer>
+ * </pre>
+ */
+public final class BlockDeviceWriter {
+ private static final String EXECUTABLE = "/data/local/tmp/block_device_writer";
+
+ /**
+ * Modifies a byte of the file directly against the backing block storage.
+ *
+ * The effect can only be observed when the page cache is read from disk again. See
+ * {@link #dropCaches} for details.
+ */
+ public static void damageFileAgainstBlockDevice(ITestDevice device, String path,
+ long offsetOfTargetingByte)
+ throws DeviceNotAvailableException {
+ assertThat(path).startsWith("/data/");
+ ITestDevice.MountPointInfo mountPoint = device.getMountPointInfo("/data");
+ ArrayList<String> args = new ArrayList<>();
+ args.add(EXECUTABLE);
+ if ("f2fs".equals(mountPoint.type)) {
+ args.add("--use-f2fs-pinning");
+ }
+ args.add(mountPoint.filesystem);
+ args.add(path);
+ args.add(Long.toString(offsetOfTargetingByte));
+ CommandResult result = device.executeShellV2Command(String.join(" ", args));
+ assertWithMessage(
+ String.format("stdout=%s\nstderr=%s", result.getStdout(), result.getStderr()))
+ .that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+ }
+
+ /**
+ * Drops file caches so that the result of {@link #damageFileAgainstBlockDevice} can be
+ * observed. If a process has an open FD or memory map of the damaged file, cache eviction won't
+ * happen and the damage cannot be observed.
+ */
+ public static void dropCaches(ITestDevice device) throws DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command(
+ "sync && echo 1 > /proc/sys/vm/drop_caches");
+ assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+ }
+
+ public static void assertFileNotOpen(ITestDevice device, String path)
+ throws DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command("lsof " + path);
+ assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+ assertThat(result.getStdout()).isEmpty();
+ }
+
+ /**
+ * Checks if the give offset of a file can be read.
+ * This method will return false if the file has fs-verity enabled and is damaged at the offset.
+ */
+ public static boolean canReadByte(ITestDevice device, String filePath, long offset)
+ throws DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command(
+ "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
+ return result.getStatus() == CommandStatus.SUCCESS;
+ }
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index d0eb9be..ab3572b 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -24,6 +24,7 @@
import android.platform.test.annotations.RootPermissionTest;
+import com.android.blockdevicewriter.BlockDeviceWriter;
import com.android.fsverity.AddFsVerityCertRule;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -334,22 +335,23 @@
long offsetFirstByte = 0;
// The first two pages should be both readable at first.
- assertTrue(canReadByte(apkPath, offsetFirstByte));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
- assertTrue(canReadByte(apkPath, offsetFirstByte + FSVERITY_PAGE_SIZE));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+ offsetFirstByte + FSVERITY_PAGE_SIZE));
}
// Damage the file directly against the block device.
damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
// Expect actual read from disk to fail but only at damaged page.
- dropCaches();
- assertFalse(canReadByte(apkPath, offsetFirstByte));
+ BlockDeviceWriter.dropCaches(mDevice);
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
long lastByteOfTheSamePage =
offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
- assertFalse(canReadByte(apkPath, lastByteOfTheSamePage));
- assertTrue(canReadByte(apkPath, lastByteOfTheSamePage + 1));
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage + 1));
}
}
@@ -362,21 +364,22 @@
long offsetOfLastByte = apkSize - 1;
// The first two pages should be both readable at first.
- assertTrue(canReadByte(apkPath, offsetOfLastByte));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
- assertTrue(canReadByte(apkPath, offsetOfLastByte - FSVERITY_PAGE_SIZE));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+ offsetOfLastByte - FSVERITY_PAGE_SIZE));
}
// Damage the file directly against the block device.
damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
// Expect actual read from disk to fail but only at damaged page.
- dropCaches();
- assertFalse(canReadByte(apkPath, offsetOfLastByte));
+ BlockDeviceWriter.dropCaches(mDevice);
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
- assertFalse(canReadByte(apkPath, firstByteOfTheSamePage));
- assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1));
+ assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage));
+ assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage - 1));
}
}
@@ -395,8 +398,8 @@
// from filesystem cache. Forcing GC workarounds the problem.
int retry = 5;
for (; retry > 0; retry--) {
- dropCaches();
- if (!canReadByte(path, kTargetOffset)) {
+ BlockDeviceWriter.dropCaches(mDevice);
+ if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) {
break;
}
try {
@@ -451,16 +454,6 @@
return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim());
}
- private void dropCaches() throws DeviceNotAvailableException {
- expectRemoteCommandToSucceed("sync && echo 1 > /proc/sys/vm/drop_caches");
- }
-
- private boolean canReadByte(String filePath, long offset) throws DeviceNotAvailableException {
- CommandResult result = mDevice.executeShellV2Command(
- "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
- return result.getStatus() == CommandStatus.SUCCESS;
- }
-
private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
CommandResult result = mDevice.executeShellV2Command(cmd);
assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index d809fe8..43a5078 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -16,8 +16,14 @@
name: "UpdatableSystemFontTest",
srcs: ["src/**/*.java"],
libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
- static_libs: ["frameworks-base-hostutils"],
+ static_libs: [
+ "block_device_writer_jar",
+ "frameworks-base-hostutils",
+ ],
test_suites: ["general-tests", "vts"],
+ target_required: [
+ "block_device_writer_module",
+ ],
data: [
":NotoColorEmojiTtf",
":UpdatableSystemFontTestCertDer",
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index efe5d70..7b919bd 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -21,6 +21,7 @@
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
+ <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
<option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
<option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" />
<option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" />
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 6d161a5..e249f8a9 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -21,7 +21,9 @@
import android.platform.test.annotations.RootPermissionTest;
+import com.android.blockdevicewriter.BlockDeviceWriter;
import com.android.fsverity.AddFsVerityCertRule;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -34,6 +36,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -126,6 +130,44 @@
TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
}
+ @Test
+ public void reboot() throws Exception {
+ expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+ String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPath).startsWith("/data/fonts/files/");
+
+ expectRemoteCommandToSucceed("stop");
+ expectRemoteCommandToSucceed("start");
+ waitUntilFontCommandIsReady();
+ String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPathAfterReboot).isEqualTo(fontPath);
+ }
+
+ @Test
+ public void reboot_clearDamagedFiles() throws Exception {
+ expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+ String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPath).startsWith("/data/fonts/files/");
+ assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isTrue();
+
+ BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), fontPath, 0);
+ expectRemoteCommandToSucceed("stop");
+ // We have to make sure system_server is gone before dropping caches, because system_server
+ // process holds font memory maps and prevents cache eviction.
+ waitUntilSystemServerIsGone();
+ BlockDeviceWriter.assertFileNotOpen(getDevice(), fontPath);
+ BlockDeviceWriter.dropCaches(getDevice());
+ assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isFalse();
+
+ expectRemoteCommandToSucceed("start");
+ waitUntilFontCommandIsReady();
+ String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertWithMessage("Damaged file should be deleted")
+ .that(fontPathAfterReboot).startsWith("/system");
+ }
+
private String getFontPath(String fontFileName) throws Exception {
// TODO: add a dedicated command for testing.
String lines = expectRemoteCommandToSucceed("cmd font dump");
@@ -153,4 +195,39 @@
.that(result.getStatus())
.isNotEqualTo(CommandStatus.SUCCESS);
}
+
+ private void waitUntilFontCommandIsReady() {
+ waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+ try {
+ return getDevice().executeShellV2Command("cmd font status").getStatus()
+ == CommandStatus.SUCCESS;
+ } catch (DeviceNotAvailableException e) {
+ return false;
+ }
+ });
+ }
+
+ private void waitUntilSystemServerIsGone() {
+ waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+ try {
+ return getDevice().executeShellV2Command("pid system_server").getStatus()
+ == CommandStatus.FAILED;
+ } catch (DeviceNotAvailableException e) {
+ return false;
+ }
+ });
+ }
+
+ private void waitUntil(long timeoutMillis, Supplier<Boolean> func) {
+ long untilMillis = System.currentTimeMillis() + timeoutMillis;
+ do {
+ if (func.get()) return;
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ throw new AssertionError("Interrupted", e);
+ }
+ } while (System.currentTimeMillis() < untilMillis);
+ throw new AssertionError("Timed out");
+ }
}
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/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index fcfb4aa..6a09b02 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -35,6 +35,7 @@
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
import static android.net.NetworkRequest.Type.REQUEST;
import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -329,6 +330,9 @@
mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
+ mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); });
+ mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); });
+
mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
@@ -377,6 +381,13 @@
eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
eq(testPkgName), eq(testAttributionTag));
reset(mService);
+
+ Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+ manager.registerSystemDefaultNetworkCallback(callback, handler);
+ verify(mService).requestNetwork(eq(null),
+ eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
+ eq(testPkgName), eq(testAttributionTag));
+ reset(mService);
}
static Message makeMessage(NetworkRequest req, int messageType) {
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
new file mode 100644
index 0000000..2fd5e38
--- /dev/null
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnTransportInfoTest {
+
+ @Test
+ public void testParceling() {
+ VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+ assertParcelSane(v, 1 /* fieldCount */);
+ assertParcelingIsLossless(v);
+ }
+
+ @Test
+ public void testEqualsAndHashCode() {
+ VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+ VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE);
+ VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+ assertNotEquals(v1, v2);
+ assertEquals(v1, v3);
+ assertEquals(v1.hashCode(), v3.hashCode());
+ }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bf61634..e639a36 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -205,6 +205,7 @@
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.VpnManager;
+import android.net.VpnTransportInfo;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
@@ -361,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";
@@ -1110,7 +1118,7 @@
}
@Override
- public int getActiveAppVpnType() {
+ public int getActiveVpnType() {
return mVpnType;
}
@@ -1123,10 +1131,12 @@
private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
throws Exception {
if (mAgentRegistered) throw new IllegalStateException("already registered");
+ updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent");
mConfig = new VpnConfig();
setUids(uids);
if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
mInterface = VPN_IFNAME;
+ mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
mNetworkCapabilities);
mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
@@ -3286,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
@@ -3332,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();
@@ -3357,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
@@ -3373,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
@@ -3386,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);
@@ -3397,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);
}
@@ -3649,10 +3726,19 @@
@Test
public void testRegisterDefaultNetworkCallback() throws Exception {
+ // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+ mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+ PERMISSION_GRANTED);
+
final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
defaultNetworkCallback.assertNoCallback();
+ final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+ final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+ mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, handler);
+ systemDefaultCallback.assertNoCallback();
+
// Create a TRANSPORT_CELLULAR request to keep the mobile interface up
// whenever Wi-Fi is up. Without this, the mobile network agent is
// reaped before any other activity can take place.
@@ -3667,27 +3753,35 @@
mCellNetworkAgent.connect(true);
cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+ assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring up wifi and expect CALLBACK_AVAILABLE.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
cellNetworkCallback.assertNoCallback();
defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+ assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring down cell. Expect no default network callback, since it wasn't the default.
mCellNetworkAgent.disconnect();
cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
defaultNetworkCallback.assertNoCallback();
+ systemDefaultCallback.assertNoCallback();
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+ assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring up cell. Expect no default network callback, since it won't be the default.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
defaultNetworkCallback.assertNoCallback();
+ systemDefaultCallback.assertNoCallback();
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+ assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Bring down wifi. Expect the default network callback to notified of LOST wifi
// followed by AVAILABLE cell.
@@ -3695,19 +3789,25 @@
cellNetworkCallback.assertNoCallback();
defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
mMockVpn.establishForMyUid();
assertUidRangesUpdatedForMyUid(true);
defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+ systemDefaultCallback.assertNoCallback();
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+ assertEquals(null, systemDefaultCallback.getLastAvailableNetwork());
mMockVpn.disconnect();
defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+ systemDefaultCallback.assertNoCallback();
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
}
@@ -6134,6 +6234,10 @@
@Test
public void testVpnNetworkActive() throws Exception {
+ // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+ mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+ PERMISSION_GRANTED);
+
final int uid = Process.myUid();
final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -6141,6 +6245,7 @@
final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build();
final NetworkRequest genericRequest = new NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_NOT_VPN).build();
@@ -6154,6 +6259,8 @@
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
mCm.registerDefaultNetworkCallback(defaultCallback);
+ mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+ new Handler(ConnectivityThread.getInstanceLooper()));
defaultCallback.assertNoCallback();
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6163,6 +6270,7 @@
genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
vpnNetworkCallback.assertNoCallback();
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -6183,7 +6291,10 @@
wifiNetworkCallback.assertNoCallback();
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+ systemDefaultCallback.assertNoCallback();
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+ assertEquals(mWiFiNetworkAgent.getNetwork(),
+ systemDefaultCallback.getLastAvailableNetwork());
ranges.clear();
mMockVpn.setUids(ranges);
@@ -6200,6 +6311,7 @@
// much, but that is the reason the test here has to check for an update to the
// capabilities instead of the expected LOST then AVAILABLE.
defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+ systemDefaultCallback.assertNoCallback();
ranges.add(new UidRange(uid, uid));
mMockVpn.setUids(ranges);
@@ -6211,6 +6323,7 @@
// TODO : Here like above, AVAILABLE would be correct, but because this can't actually
// happen outside of the test, ConnectivityService does not rematch callbacks.
defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+ systemDefaultCallback.assertNoCallback();
mWiFiNetworkAgent.disconnect();
@@ -6219,6 +6332,7 @@
wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
vpnNetworkCallback.assertNoCallback();
defaultCallback.assertNoCallback();
+ systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
mMockVpn.disconnect();
@@ -6227,12 +6341,14 @@
wifiNetworkCallback.assertNoCallback();
vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+ systemDefaultCallback.assertNoCallback();
assertEquals(null, mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(genericNetworkCallback);
mCm.unregisterNetworkCallback(wifiNetworkCallback);
mCm.unregisterNetworkCallback(vpnNetworkCallback);
mCm.unregisterNetworkCallback(defaultCallback);
+ mCm.unregisterNetworkCallback(systemDefaultCallback);
}
@Test
@@ -7283,6 +7399,7 @@
}
private void establishLegacyLockdownVpn() throws Exception {
+ mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY);
// The legacy lockdown VPN only supports userId 0.
final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
mMockVpn.registerAgent(ranges);
@@ -7395,6 +7512,9 @@
assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
+ VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo();
+ assertNotNull(ti);
+ assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type);
// Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
final LinkProperties wifiLp = new LinkProperties();
@@ -8521,11 +8641,7 @@
final int myUid = Process.myUid();
setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM);
- try {
- mService.getConnectionOwnerUid(getTestConnectionInfo());
- fail("Expected SecurityException for non-VpnService app");
- } catch (SecurityException expected) {
- }
+ assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
}
@Test
@@ -8533,11 +8649,7 @@
final int myUid = Process.myUid();
setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE);
- try {
- mService.getConnectionOwnerUid(getTestConnectionInfo());
- fail("Expected SecurityException for non-VpnService app");
- } catch (SecurityException expected) {
- }
+ assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
}
@Test
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/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 73cc9f1..cffd2d1d 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -25,6 +25,7 @@
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 static org.mockito.ArgumentMatchers.any;
@@ -74,6 +75,7 @@
import android.net.UidRangeParcel;
import android.net.VpnManager;
import android.net.VpnService;
+import android.net.VpnTransportInfo;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.os.Build.VERSION_CODES;
@@ -984,6 +986,13 @@
startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
}
+ private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
+ assertNotNull(nc);
+ VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
+ assertNotNull(ti);
+ assertEquals(type, ti.type);
+ }
+
public void startRacoon(final String serverAddr, final String expectedAddr)
throws Exception {
final ConditionVariable legacyRunnerReady = new ConditionVariable();
@@ -1020,8 +1029,10 @@
// Now wait for the runner to be ready before testing for the route.
ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
+ ArgumentCaptor<NetworkCapabilities> ncCaptor =
+ ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
- lpCaptor.capture(), any(), anyInt(), any(), anyInt());
+ lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt());
// In this test the expected address is always v4 so /32.
// Note that the interface needs to be specified because RouteInfo objects stored in
@@ -1031,6 +1042,8 @@
final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
actualRoutes.contains(expectedRoute));
+
+ assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY);
} finally {
// Now interrupt the thread, unblock the runner and clean up.
vpn.mVpnRunner.exitVpnRunner();
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index f9db408..7dada9d 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -65,7 +65,7 @@
ArgumentCaptor.forClass(IVcnUnderlyingNetworkPolicyListener.class);
verify(mMockVcnManagementService).addVcnUnderlyingNetworkPolicyListener(captor.capture());
- assertTrue(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+ assertTrue(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
IVcnUnderlyingNetworkPolicyListener listenerWrapper = captor.getValue();
listenerWrapper.onPolicyChanged();
@@ -78,7 +78,7 @@
mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
- assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+ assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
verify(mMockVcnManagementService)
.addVcnUnderlyingNetworkPolicyListener(
any(IVcnUnderlyingNetworkPolicyListener.class));
@@ -88,7 +88,7 @@
public void testRemoveVcnUnderlyingNetworkPolicyListenerUnknownListener() throws Exception {
mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
- assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+ assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
verify(mMockVcnManagementService, never())
.addVcnUnderlyingNetworkPolicyListener(
any(IVcnUnderlyingNetworkPolicyListener.class));
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 4859644..c290bff 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -16,6 +16,10 @@
package com.android.server;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
import static com.android.server.vcn.VcnTestUtils.setupSystemService;
@@ -106,6 +110,7 @@
Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
private static final int TEST_SUBSCRIPTION_ID = 1;
+ private static final int TEST_SUBSCRIPTION_ID_2 = 2;
private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
new SubscriptionInfo(
TEST_SUBSCRIPTION_ID /* id */,
@@ -537,59 +542,121 @@
Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
}
- private void verifyMergedNetworkCapabilitiesIsVcnManaged(
- NetworkCapabilities mergedCapabilities, @Transport int transportType) {
+ private void verifyMergedNetworkCapabilities(
+ NetworkCapabilities mergedCapabilities,
+ @Transport int transportType,
+ boolean isVcnManaged,
+ boolean isRestricted) {
assertTrue(mergedCapabilities.hasTransport(transportType));
- assertFalse(
+ assertEquals(
+ !isVcnManaged,
mergedCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+ assertEquals(
+ !isRestricted,
+ mergedCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
+ private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) {
+ setUpVcnSubscription(subId, subGrp);
+ final Vcn vcn = startAndGetVcnInstance(subGrp);
+ doReturn(isVcnActive).when(vcn).isActive();
+ }
+
+ private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport(
+ int subId, ParcelUuid subGrp, boolean isVcnActive, int transport) {
+ setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive);
+
+ final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
+ ncBuilder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+ if (transport == TRANSPORT_CELLULAR) {
+ ncBuilder
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
+ } else if (transport == TRANSPORT_WIFI) {
+ WifiInfo wifiInfo = mock(WifiInfo.class);
+ when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
+ when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
+
+ ncBuilder
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(wifiInfo);
+ } else {
+ throw new IllegalArgumentException("Unknown transport");
+ }
+
+ return mVcnMgmtSvc.getUnderlyingNetworkPolicy(ncBuilder.build(), new LinkProperties());
}
@Test
public void testGetUnderlyingNetworkPolicyCellular() throws Exception {
- setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
-
- NetworkCapabilities nc =
- new NetworkCapabilities.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
- .build();
-
- VcnUnderlyingNetworkPolicy policy =
- mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+ final VcnUnderlyingNetworkPolicy policy =
+ startVcnAndGetPolicyForTransport(
+ TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_CELLULAR);
assertFalse(policy.isTeardownRequested());
- verifyMergedNetworkCapabilitiesIsVcnManaged(
- policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR);
+ verifyMergedNetworkCapabilities(
+ policy.getMergedNetworkCapabilities(),
+ TRANSPORT_CELLULAR,
+ true /* isVcnManaged */,
+ false /* isRestricted */);
+ }
+
+ @Test
+ public void testGetUnderlyingNetworkPolicyCellular_safeMode() throws Exception {
+ final VcnUnderlyingNetworkPolicy policy =
+ startVcnAndGetPolicyForTransport(
+ TEST_SUBSCRIPTION_ID,
+ TEST_UUID_2,
+ false /* isActive */,
+ TRANSPORT_CELLULAR);
+
+ assertFalse(policy.isTeardownRequested());
+ verifyMergedNetworkCapabilities(
+ policy.getMergedNetworkCapabilities(),
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false /* isVcnManaged */,
+ false /* isRestricted */);
}
@Test
public void testGetUnderlyingNetworkPolicyWifi() throws Exception {
- setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
-
- WifiInfo wifiInfo = mock(WifiInfo.class);
- when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
- when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
- NetworkCapabilities nc =
- new NetworkCapabilities.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
- .setTransportInfo(wifiInfo)
- .build();
-
- VcnUnderlyingNetworkPolicy policy =
- mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+ final VcnUnderlyingNetworkPolicy policy =
+ startVcnAndGetPolicyForTransport(
+ TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_WIFI);
assertFalse(policy.isTeardownRequested());
- verifyMergedNetworkCapabilitiesIsVcnManaged(
- policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
+ verifyMergedNetworkCapabilities(
+ policy.getMergedNetworkCapabilities(),
+ NetworkCapabilities.TRANSPORT_WIFI,
+ true /* isVcnManaged */,
+ true /* isRestricted */);
+ }
+
+ @Test
+ public void testGetUnderlyingNetworkPolicyVcnWifi_safeMode() throws Exception {
+ final VcnUnderlyingNetworkPolicy policy =
+ startVcnAndGetPolicyForTransport(
+ TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI);
+
+ assertFalse(policy.isTeardownRequested());
+ verifyMergedNetworkCapabilities(
+ policy.getMergedNetworkCapabilities(),
+ NetworkCapabilities.TRANSPORT_WIFI,
+ false /* isVcnManaged */,
+ true /* isRestricted */);
}
@Test
public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
+ setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */);
+
NetworkCapabilities nc =
new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
.build();
VcnUnderlyingNetworkPolicy policy =
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
deleted file mode 100755
index 28ff606..0000000
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ /dev/null
@@ -1,383 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-"""Generate API lists for non-SDK API enforcement."""
-import argparse
-from collections import defaultdict, namedtuple
-import functools
-import os
-import re
-import sys
-
-# Names of flags recognized by the `hiddenapi` tool.
-FLAG_SDK = 'sdk'
-FLAG_UNSUPPORTED = 'unsupported'
-FLAG_BLOCKED = 'blocked'
-FLAG_MAX_TARGET_O = 'max-target-o'
-FLAG_MAX_TARGET_P = 'max-target-p'
-FLAG_MAX_TARGET_Q = 'max-target-q'
-FLAG_MAX_TARGET_R = 'max-target-r'
-FLAG_CORE_PLATFORM_API = 'core-platform-api'
-FLAG_PUBLIC_API = 'public-api'
-FLAG_SYSTEM_API = 'system-api'
-FLAG_TEST_API = 'test-api'
-
-# List of all known flags.
-FLAGS_API_LIST = [
- FLAG_SDK,
- FLAG_UNSUPPORTED,
- FLAG_BLOCKED,
- FLAG_MAX_TARGET_O,
- FLAG_MAX_TARGET_P,
- FLAG_MAX_TARGET_Q,
- FLAG_MAX_TARGET_R,
-]
-ALL_FLAGS = FLAGS_API_LIST + [
- FLAG_CORE_PLATFORM_API,
- FLAG_PUBLIC_API,
- FLAG_SYSTEM_API,
- FLAG_TEST_API,
-]
-
-FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
-ALL_FLAGS_SET = set(ALL_FLAGS)
-
-# Option specified after one of FLAGS_API_LIST to indicate that
-# only known and otherwise unassigned entries should be assign the
-# given flag.
-# For example, the max-target-P list is checked in as it was in P,
-# but signatures have changes since then. The flag instructs this
-# script to skip any entries which do not exist any more.
-FLAG_IGNORE_CONFLICTS = "ignore-conflicts"
-
-# Option specified after one of FLAGS_API_LIST to express that all
-# apis within a given set of packages should be assign the given flag.
-FLAG_PACKAGES = "packages"
-
-# Option specified after one of FLAGS_API_LIST to indicate an extra
-# tag that should be added to the matching APIs.
-FLAG_TAG = "tag"
-
-# Regex patterns of fields/methods used in serialization. These are
-# considered public API despite being hidden.
-SERIALIZATION_PATTERNS = [
- r'readObject\(Ljava/io/ObjectInputStream;\)V',
- r'readObjectNoData\(\)V',
- r'readResolve\(\)Ljava/lang/Object;',
- r'serialVersionUID:J',
- r'serialPersistentFields:\[Ljava/io/ObjectStreamField;',
- r'writeObject\(Ljava/io/ObjectOutputStream;\)V',
- r'writeReplace\(\)Ljava/lang/Object;',
-]
-
-# Single regex used to match serialization API. It combines all the
-# SERIALIZATION_PATTERNS into a single regular expression.
-SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$')
-
-# Predicates to be used with filter_apis.
-HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags)
-IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
-
-
-class StoreOrderedOptions(argparse.Action):
- """An argparse action that stores a number of option arguments in the order that
- they were specified.
- """
- def __call__(self, parser, args, values, option_string = None):
- items = getattr(args, self.dest, None)
- if items is None:
- items = []
- items.append([option_string.lstrip('-'), values])
- setattr(args, self.dest, items)
-
-def get_args():
- """Parses command line arguments.
-
- Returns:
- Namespace: dictionary of parsed arguments
- """
- parser = argparse.ArgumentParser()
- parser.add_argument('--output', required=True)
- parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE',
- help='CSV files to be merged into output')
-
- for flag in ALL_FLAGS:
- parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE',
- action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"')
- parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0,
- action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned '
- 'entries should be assign the given flag. Must follow a list of entries and applies '
- 'to the preceding such list.')
- parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0,
- action=StoreOrderedOptions, help='Indicates that the previous list of entries '
- 'is a list of packages. All members in those packages will be given the flag. '
- 'Must follow a list of entries and applies to the preceding such list.')
- parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1,
- action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. '
- 'Must follow a list of entries and applies to the preceding such list.')
-
- return parser.parse_args()
-
-
-def read_lines(filename):
- """Reads entire file and return it as a list of lines.
-
- Lines which begin with a hash are ignored.
-
- Args:
- filename (string): Path to the file to read from.
-
- Returns:
- Lines of the file as a list of string.
- """
- with open(filename, 'r') as f:
- lines = f.readlines();
- lines = filter(lambda line: not line.startswith('#'), lines)
- lines = map(lambda line: line.strip(), lines)
- return set(lines)
-
-
-def write_lines(filename, lines):
- """Writes list of lines into a file, overwriting the file if it exists.
-
- Args:
- filename (string): Path to the file to be writting into.
- lines (list): List of strings to write into the file.
- """
- lines = map(lambda line: line + '\n', lines)
- with open(filename, 'w') as f:
- f.writelines(lines)
-
-
-def extract_package(signature):
- """Extracts the package from a signature.
-
- Args:
- signature (string): JNI signature of a method or field.
-
- Returns:
- The package name of the class containing the field/method.
- """
- full_class_name = signature.split(";->")[0]
- # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
- if (full_class_name[0] != "L"):
- raise ValueError("Expected to start with 'L': %s" % full_class_name)
- full_class_name = full_class_name[1:]
- # If full_class_name doesn't contain '/', then package_name will be ''.
- package_name = full_class_name.rpartition("/")[0]
- return package_name.replace('/', '.')
-
-
-class FlagsDict:
- def __init__(self):
- self._dict_keyset = set()
- self._dict = defaultdict(set)
-
- def _check_entries_set(self, keys_subset, source):
- assert isinstance(keys_subset, set)
- assert keys_subset.issubset(self._dict_keyset), (
- "Error: {} specifies signatures not present in code:\n"
- "{}"
- "Please visit go/hiddenapi for more information.").format(
- source, "".join(map(lambda x: " " + str(x) + "\n", keys_subset - self._dict_keyset)))
-
- def _check_flags_set(self, flags_subset, source):
- assert isinstance(flags_subset, set)
- assert flags_subset.issubset(ALL_FLAGS_SET), (
- "Error processing: {}\n"
- "The following flags were not recognized: \n"
- "{}\n"
- "Please visit go/hiddenapi for more information.").format(
- source, "\n".join(flags_subset - ALL_FLAGS_SET))
-
- def filter_apis(self, filter_fn):
- """Returns APIs which match a given predicate.
-
- This is a helper function which allows to filter on both signatures (keys) and
- flags (values). The built-in filter() invokes the lambda only with dict's keys.
-
- Args:
- filter_fn : Function which takes two arguments (signature/flags) and returns a boolean.
-
- Returns:
- A set of APIs which match the predicate.
- """
- return set(filter(lambda x: filter_fn(x, self._dict[x]), self._dict_keyset))
-
- def get_valid_subset_of_unassigned_apis(self, api_subset):
- """Sanitizes a key set input to only include keys which exist in the dictionary
- and have not been assigned any API list flags.
-
- Args:
- entries_subset (set/list): Key set to be sanitized.
-
- Returns:
- Sanitized key set.
- """
- assert isinstance(api_subset, set)
- return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED))
-
- def generate_csv(self):
- """Constructs CSV entries from a dictionary.
-
- Old versions of flags are used to generate the file.
-
- Returns:
- List of lines comprising a CSV file. See "parse_and_merge_csv" for format description.
- """
- lines = []
- for api in self._dict:
- flags = sorted(self._dict[api])
- lines.append(",".join([api] + flags))
- return sorted(lines)
-
- def parse_and_merge_csv(self, csv_lines, source = "<unknown>"):
- """Parses CSV entries and merges them into a given dictionary.
-
- The expected CSV format is:
- <api signature>,<flag1>,<flag2>,...,<flagN>
-
- Args:
- csv_lines (list of strings): Lines read from a CSV file.
- source (string): Origin of `csv_lines`. Will be printed in error messages.
-
- Throws:
- AssertionError if parsed flags are invalid.
- """
- # Split CSV lines into arrays of values.
- csv_values = [ line.split(',') for line in csv_lines ]
-
- # Update the full set of API signatures.
- self._dict_keyset.update([ csv[0] for csv in csv_values ])
-
- # Check that all flags are known.
- csv_flags = set()
- for csv in csv_values:
- csv_flags.update(csv[1:])
- self._check_flags_set(csv_flags, source)
-
- # Iterate over all CSV lines, find entry in dict and append flags to it.
- for csv in csv_values:
- flags = csv[1:]
- if (FLAG_PUBLIC_API in flags) or (FLAG_SYSTEM_API in flags):
- flags.append(FLAG_SDK)
- self._dict[csv[0]].update(flags)
-
- def assign_flag(self, flag, apis, source="<unknown>", tag = None):
- """Assigns a flag to given subset of entries.
-
- Args:
- flag (string): One of ALL_FLAGS.
- apis (set): Subset of APIs to receive the flag.
- source (string): Origin of `entries_subset`. Will be printed in error messages.
-
- Throws:
- AssertionError if parsed API signatures of flags are invalid.
- """
- # Check that all APIs exist in the dict.
- self._check_entries_set(apis, source)
-
- # Check that the flag is known.
- self._check_flags_set(set([ flag ]), source)
-
- # Iterate over the API subset, find each entry in dict and assign the flag to it.
- for api in apis:
- self._dict[api].add(flag)
- if tag:
- self._dict[api].add(tag)
-
-
-FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag'))
-
-def parse_ordered_flags(ordered_flags):
- r = []
- currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None
- for flag_value in ordered_flags:
- flag, value = flag_value[0], flag_value[1]
- if flag in ALL_FLAGS_SET:
- if currentflag:
- r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
- ignore_conflicts, packages, tag = False, False, None
- currentflag = flag
- file = value
- else:
- if currentflag is None:
- raise argparse.ArgumentError('--%s is only allowed after one of %s' % (
- flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET])))
- if flag == FLAG_IGNORE_CONFLICTS:
- ignore_conflicts = True
- elif flag == FLAG_PACKAGES:
- packages = True
- elif flag == FLAG_TAG:
- tag = value[0]
-
-
- if currentflag:
- r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
- return r
-
-
-def main(argv):
- # Parse arguments.
- args = vars(get_args())
- flagfiles = parse_ordered_flags(args['ordered_flags'])
-
- # Initialize API->flags dictionary.
- flags = FlagsDict()
-
- # Merge input CSV files into the dictionary.
- # Do this first because CSV files produced by parsing API stubs will
- # contain the full set of APIs. Subsequent additions from text files
- # will be able to detect invalid entries, and/or filter all as-yet
- # unassigned entries.
- for filename in args["csv"]:
- flags.parse_and_merge_csv(read_lines(filename), filename)
-
- # Combine inputs which do not require any particular order.
- # (1) Assign serialization API to SDK.
- flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION))
-
- # (2) Merge text files with a known flag into the dictionary.
- for info in flagfiles:
- if (not info.ignore_conflicts) and (not info.packages):
- flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag)
-
- # Merge text files where conflicts should be ignored.
- # This will only assign the given flag if:
- # (a) the entry exists, and
- # (b) it has not been assigned any other flag.
- # Because of (b), this must run after all strict assignments have been performed.
- for info in flagfiles:
- if info.ignore_conflicts:
- valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file))
- flags.assign_flag(info.flag, valid_entries, filename, info.tag)
-
- # All members in the specified packages will be assigned the appropriate flag.
- for info in flagfiles:
- if info.packages:
- packages_needing_list = set(read_lines(info.file))
- should_add_signature_to_list = lambda sig,lists: extract_package(
- sig) in packages_needing_list and not lists
- valid_entries = flags.filter_apis(should_add_signature_to_list)
- flags.assign_flag(info.flag, valid_entries, info.file, info.tag)
-
- # Mark all remaining entries as blocked.
- flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))
-
- # Write output.
- write_lines(args["output"], flags.generate_csv())
-
-if __name__ == "__main__":
- main(sys.argv)
diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py
deleted file mode 100755
index 82d117f..0000000
--- a/tools/hiddenapi/generate_hiddenapi_lists_test.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-"""Unit tests for Hidden API list generation."""
-import unittest
-from generate_hiddenapi_lists import *
-
-class TestHiddenapiListGeneration(unittest.TestCase):
-
- def test_filter_apis(self):
- # Initialize flags so that A and B are put on the whitelist and
- # C, D, E are left unassigned. Try filtering for the unassigned ones.
- flags = FlagsDict()
- flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B,' + FLAG_SDK,
- 'C', 'D', 'E'])
- filter_set = flags.filter_apis(lambda api, flags: not flags)
- self.assertTrue(isinstance(filter_set, set))
- self.assertEqual(filter_set, set([ 'C', 'D', 'E' ]))
-
- def test_get_valid_subset_of_unassigned_keys(self):
- # Create flags where only A is unassigned.
- flags = FlagsDict()
- flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B', 'C'])
- flags.assign_flag(FLAG_UNSUPPORTED, set(['C']))
- self.assertEqual(flags.generate_csv(),
- [ 'A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED ])
-
- # Check three things:
- # (1) B is selected as valid unassigned
- # (2) A is not selected because it is assigned 'whitelist'
- # (3) D is not selected because it is not a valid key
- self.assertEqual(
- flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ]))
-
- def test_parse_and_merge_csv(self):
- flags = FlagsDict()
-
- # Test empty CSV entry.
- self.assertEqual(flags.generate_csv(), [])
-
- # Test new additions.
- flags.parse_and_merge_csv([
- 'A,' + FLAG_UNSUPPORTED,
- 'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O,
- 'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API,
- 'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
- 'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
- ])
- self.assertEqual(flags.generate_csv(), [
- 'A,' + FLAG_UNSUPPORTED,
- 'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O,
- 'C,' + FLAG_SYSTEM_API + ',' + FLAG_SDK,
- 'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
- 'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
- ])
-
- # Test unknown flag.
- with self.assertRaises(AssertionError):
- flags.parse_and_merge_csv([ 'Z,foo' ])
-
- def test_assign_flag(self):
- flags = FlagsDict()
- flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B'])
-
- # Test new additions.
- flags.assign_flag(FLAG_UNSUPPORTED, set([ 'A', 'B' ]))
- self.assertEqual(flags.generate_csv(),
- [ 'A,' + FLAG_UNSUPPORTED + "," + FLAG_SDK, 'B,' + FLAG_UNSUPPORTED ])
-
- # Test invalid API signature.
- with self.assertRaises(AssertionError):
- flags.assign_flag(FLAG_SDK, set([ 'C' ]))
-
- # Test invalid flag.
- with self.assertRaises(AssertionError):
- flags.assign_flag('foo', set([ 'A' ]))
-
- def test_extract_package(self):
- signature = 'Lcom/foo/bar/Baz;->method1()Lcom/bar/Baz;'
- expected_package = 'com.foo.bar'
- self.assertEqual(extract_package(signature), expected_package)
-
- signature = 'Lcom/foo1/bar/MyClass;->method2()V'
- expected_package = 'com.foo1.bar'
- self.assertEqual(extract_package(signature), expected_package)
-
- signature = 'Lcom/foo_bar/baz/MyClass;->method3()V'
- expected_package = 'com.foo_bar.baz'
- self.assertEqual(extract_package(signature), expected_package)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py
deleted file mode 100755
index 6a5b0e1..0000000
--- a/tools/hiddenapi/merge_csv.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-"""
-Merge multiple CSV files, possibly with different columns.
-"""
-
-import argparse
-import csv
-import io
-
-from zipfile import ZipFile
-
-args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
-args_parser.add_argument('--header', help='Comma separated field names; '
- 'if missing determines the header from input files.')
-args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.')
-args_parser.add_argument('--output', help='Output file for merged CSV.',
- default='-', type=argparse.FileType('w'))
-args_parser.add_argument('files', nargs=argparse.REMAINDER)
-args = args_parser.parse_args()
-
-
-def dict_reader(input):
- return csv.DictReader(input, delimiter=',', quotechar='|')
-
-
-if args.zip_input and len(args.files) > 0:
- raise ValueError('Expecting either a single ZIP with CSV files'
- ' or a list of CSV files as input; not both.')
-
-csv_readers = []
-if len(args.files) > 0:
- for file in args.files:
- csv_readers.append(dict_reader(open(file, 'r')))
-elif args.zip_input:
- with ZipFile(args.zip_input) as zip:
- for entry in zip.namelist():
- if entry.endswith('.uau'):
- csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
-
-headers = set()
-if args.header:
- fieldnames = args.header.split(',')
-else:
- # Build union of all columns from source files:
- for reader in csv_readers:
- headers = headers.union(reader.fieldnames)
- fieldnames = sorted(headers)
-
-# Concatenate all files to output:
-writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
- dialect='unix', fieldnames=fieldnames)
-writer.writeheader()
-for reader in csv_readers:
- for row in reader:
- writer.writerow(row)