Merge "Adjust colors for accessibility" into sc-dev
diff --git a/Android.bp b/Android.bp
index d170913..202e548 100644
--- a/Android.bp
+++ b/Android.bp
@@ -632,6 +632,7 @@
],
sdk_version: "core_platform",
static_libs: [
+ "bouncycastle-repackaged-unbundled",
"framework-internal-utils",
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
// in favor of an API stubs dependency in java_library "framework" below.
@@ -1294,6 +1295,18 @@
],
}
+python_binary_host {
+ name: "update_font_metadata",
+ defaults: ["base_default"],
+ main: "tools/fonts/update_font_metadata.py",
+ srcs: [
+ "tools/fonts/update_font_metadata.py",
+ ],
+ libs: [
+ "fontTools",
+ ],
+}
+
filegroup {
name: "framework-media-annotation-srcs",
srcs: [
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cdf5df6c..30ed7de 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -8,6 +8,7 @@
cmds/input/
cmds/uinput/
core/jni/
+ libs/hwui/
libs/input/
native/
services/core/jni/
diff --git a/apct-tests/perftests/core/OWNERS b/apct-tests/perftests/core/OWNERS
new file mode 100644
index 0000000..18486af
--- /dev/null
+++ b/apct-tests/perftests/core/OWNERS
@@ -0,0 +1 @@
+include /graphics/java/android/graphics/fonts/OWNERS
diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
index 9e519f7..1d94d7e 100644
--- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
@@ -178,7 +178,7 @@
// For testing, just disable enforcement to avoid hooking up to compat framework
ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
}
- val parser = ParsingPackageUtils(false, null, null,
+ val parser = ParsingPackageUtils(false, null, null, emptyList(),
object : ParsingPackageUtils.Callback {
override fun hasFeature(feature: String) = true
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index c37f6d9..a2dc1c2 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -19,14 +19,12 @@
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
import android.perftests.utils.PerfManualStatusReporter;
import android.view.Display;
-import android.view.DisplayCutout;
import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
@@ -85,9 +83,6 @@
private static class TestWindow extends BaseIWindow {
final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
final InsetsState mRequestedVisibility = new InsetsState();
- final Rect mOutFrame = new Rect();
- final DisplayCutout.ParcelableWrapper mOutDisplayCutout =
- new DisplayCutout.ParcelableWrapper();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
@@ -107,7 +102,7 @@
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
- Display.DEFAULT_DISPLAY, mRequestedVisibility, mOutFrame, inputChannel,
+ Display.DEFAULT_DISPLAY, mRequestedVisibility, inputChannel,
mOutInsetsState, mOutControls);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/Android.bp b/apex/Android.bp
index 1876110..8310ba7 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -15,189 +15,3 @@
package {
default_visibility: [":__subpackages__"],
}
-
-mainline_stubs_args =
- "--error UnhiddenSystemApi " +
- "--hide BroadcastBehavior " +
- "--hide CallbackInterface " +
- "--hide DeprecationMismatch " +
- "--hide HiddenSuperclass " +
- "--hide HiddenTypedefConstant " +
- "--hide HiddenTypeParameter " +
- "--hide MissingPermission " +
- "--hide RequiresPermission " +
- "--hide SdkConstant " +
- "--hide Todo " +
- "--hide Typo " +
- "--hide UnavailableSymbol "
-
-// TODO: modularize this so not every module has the same whitelist
-framework_packages_to_document = [
- "android",
- "dalvik",
- "java",
- "javax",
- "junit",
- "org.apache.http",
- "org.json",
- "org.w3c.dom",
- "org.xml.sax",
- "org.xmlpull",
-]
-
-// TODO: remove the hiding when server classes are cleaned up.
-mainline_framework_stubs_args =
- mainline_stubs_args +
- "--hide-package com.android.server "
-
-priv_apps = " " +
- "--show-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
- "\\) "
-
-module_libs = " " +
- " --show-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
- "\\)" +
- " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
- "\\) "
-
-mainline_service_stubs_args =
- mainline_stubs_args +
- "--show-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.SYSTEM_SERVER" +
- "\\) " +
- "--hide-annotation android.annotation.Hide " +
- "--hide InternalClasses " // com.android.* classes are okay in this interface
-
-// Defaults common to all mainline module java_sdk_library instances.
-java_defaults {
- name: "framework-module-common-defaults",
-
- // Additional annotations used for compiling both the implementation and the
- // stubs libraries.
- libs: ["framework-annotations-lib"],
-
- // Framework modules are not generally shared libraries, i.e. they are not
- // intended, and must not be allowed, to be used in a <uses-library> manifest
- // entry.
- shared_library: false,
-
- // Prevent dependencies that do not specify an sdk_version from accessing the
- // implementation library by default and force them to use stubs instead.
- default_to_stubs: true,
-
- // Enable api lint. This will eventually become the default for java_sdk_library
- // but it cannot yet be turned on because some usages have not been cleaned up.
- // TODO(b/156126315) - Remove when no longer needed.
- api_lint: {
- enabled: true,
- },
-
- // The API scope specific properties.
- public: {
- enabled: true,
- sdk_version: "module_current",
- },
-
- // installable implies we'll create a non-apex (platform) variant, which
- // we shouldn't ordinarily need (and it can create issues), so disable that.
- installable: false,
-
- // Configure framework module specific metalava options.
- droiddoc_options: [mainline_stubs_args],
-
- annotations_enabled: true,
-
- // Allow access to the stubs from anywhere
- visibility: ["//visibility:public"],
- stubs_library_visibility: ["//visibility:public"],
-
- // Hide impl library and stub sources
- impl_library_visibility: [
- ":__pkg__",
- "//frameworks/base", // For framework-all
- ],
- stubs_source_visibility: ["//visibility:private"],
-
- defaults_visibility: ["//visibility:private"],
-
- // Collates API usages from each module for further analysis.
- plugins: ["java_api_finder"],
-
- // Mainline modules should only rely on 'module_lib' APIs provided by other modules
- // and the non updatable parts of the platform.
- sdk_version: "module_current",
-}
-
-// Defaults for mainline module provided java_sdk_library instances.
-java_defaults {
- name: "framework-module-defaults",
- defaults: ["framework-module-common-defaults"],
-
- system: {
- enabled: true,
- sdk_version: "module_current",
- },
- module_lib: {
- enabled: true,
- sdk_version: "module_current",
- },
- defaults_visibility: [
- ":__subpackages__",
- "//frameworks/base/libs/hwui",
- "//frameworks/base/wifi",
- "//packages/modules:__subpackages__",
- "//packages/providers/MediaProvider:__subpackages__",
- ],
-}
-
-// Defaults for mainline module system server provided java_sdk_library instances.
-java_defaults {
- name: "framework-system-server-module-defaults",
- defaults: ["framework-module-common-defaults"],
-
- system_server: {
- enabled: true,
- sdk_version: "module_current",
- },
- defaults_visibility: [
- ":__subpackages__",
- "//packages/modules:__subpackages__",
- ],
-}
-
-stubs_defaults {
- name: "service-module-stubs-srcs-defaults",
- args: mainline_service_stubs_args,
- installable: false,
- annotations_enabled: true,
- merge_annotations_dirs: [
- "metalava-manual",
- ],
- filter_packages: ["com.android."],
- check_api: {
- current: {
- api_file: "api/current.txt",
- removed_api_file: "api/removed.txt",
- },
- api_lint: {
- enabled: true,
- },
- },
- dist: {
- targets: ["sdk", "win_sdk"],
- dir: "apistubs/android/system-server/api",
- },
-}
-
-// Empty for now, but a convenient place to add rules for all
-// module java_library system_server stub libs.
-java_defaults {
- name: "service-module-stubs-defaults",
- dist: {
- targets: ["sdk", "win_sdk"],
- dir: "apistubs/android/system-server",
- },
-}
diff --git a/apex/OWNERS b/apex/OWNERS
index bde2bec..b3e81b9 100644
--- a/apex/OWNERS
+++ b/apex/OWNERS
@@ -1,8 +1 @@
-# Mainline modularization team
-
-andreionea@google.com
-dariofreni@google.com
-hansson@google.com
-mathewi@google.com
-pedroql@google.com
-satayev@google.com
+file:platform/packages/modules/common:/OWNERS
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index b1394c1..a3b8013 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -143,11 +143,11 @@
method public void close();
method public void getByUri(@NonNull android.app.appsearch.GetByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>);
method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<android.app.appsearch.AppSearchSchema>>>);
- method public void putDocuments(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
- method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
- method public void removeByQuery(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
- method public void removeByUri(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
+ method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
+ method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
+ method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method @NonNull public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
+ method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
}
@@ -216,7 +216,7 @@
public class GlobalSearchSession implements java.io.Closeable {
method public void close();
- method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
+ method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
}
public class PackageIdentifier {
@@ -226,13 +226,13 @@
}
public final class PutDocumentsRequest {
- method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getDocuments();
+ method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getGenericDocuments();
}
public static final class PutDocumentsRequest.Builder {
ctor public PutDocumentsRequest.Builder();
- method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull android.app.appsearch.GenericDocument...);
- method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
+ method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull android.app.appsearch.GenericDocument...);
+ method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
method @NonNull public android.app.appsearch.PutDocumentsRequest build();
}
@@ -290,14 +290,14 @@
}
public final class SearchSpec {
+ method @NonNull public java.util.List<java.lang.String> getFilterNamespaces();
method @NonNull public java.util.List<java.lang.String> getFilterPackageNames();
+ method @NonNull public java.util.List<java.lang.String> getFilterSchemas();
method public int getMaxSnippetSize();
- method @NonNull public java.util.List<java.lang.String> getNamespaces();
method public int getOrder();
method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections();
method public int getRankingStrategy();
method public int getResultCountPerPage();
- method @NonNull public java.util.List<java.lang.String> getSchemaTypes();
method public int getSnippetCount();
method public int getSnippetCountPerProperty();
method public int getTermMatch();
@@ -316,14 +316,14 @@
public static final class SearchSpec.Builder {
ctor public SearchSpec.Builder();
+ method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.lang.String...);
+ method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>);
- method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.lang.String...);
- method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.lang.String...);
+ method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
- method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.lang.String...);
- method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec build();
method @NonNull public android.app.appsearch.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_SIZE_LIMIT) int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int);
@@ -344,8 +344,8 @@
public static final class SetSchemaRequest.Builder {
ctor public SetSchemaRequest.Builder();
- method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema...);
- method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
+ method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull android.app.appsearch.AppSearchSchema...);
+ method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
method @NonNull public android.app.appsearch.SetSchemaRequest build();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.Migrator);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 814800e..69d4e53 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -270,12 +270,12 @@
* they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
* @throws RuntimeException If an error occurred during the execution.
* @hide
- * @deprecated use {@link AppSearchSession#putDocuments} instead.
+ * @deprecated use {@link AppSearchSession#put} instead.
*/
public AppSearchBatchResult<String, Void> putDocuments(@NonNull PutDocumentsRequest request) {
// TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in
// one big list.
- List<GenericDocument> documents = request.getDocuments();
+ List<GenericDocument> documents = request.getGenericDocuments();
List<Bundle> documentBundles = new ArrayList<>(documents.size());
for (int i = 0; i < documents.size(); i++) {
documentBundles.add(documents.get(i).getBundle());
@@ -330,7 +330,7 @@
DEFAULT_DATABASE_NAME,
request.getNamespace(),
uris,
- request.getProjectionsVisibleToPackagesInternal(),
+ request.getProjectionsInternal(),
mContext.getUserId(),
new IAppSearchBatchResultCallback.Stub() {
public void onResult(AppSearchBatchResult result) {
@@ -465,7 +465,7 @@
* {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
* @throws RuntimeException If an error occurred during the execution.
* @hide
- * @deprecated use {@link AppSearchSession#removeByUri} instead.
+ * @deprecated use {@link AppSearchSession#remove} instead.
*/
public AppSearchBatchResult<String, Void> removeByUri(@NonNull RemoveByUriRequest request) {
List<String> uris = new ArrayList<>(request.getUris());
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 670f8b9..8723515 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -102,7 +102,7 @@
}
/**
- * Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
+ * Sets the schema that will be used by documents provided to the {@link #put} method.
*
* <p>The schema provided here is compared to the stored copy of the schema previously supplied
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -268,7 +268,7 @@
* {@link Throwable} if an unexpected internal error occurred in AppSearch
* service.
*/
- public void putDocuments(
+ public void put(
@NonNull PutDocumentsRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull BatchResultCallback<String, Void> callback) {
@@ -276,7 +276,7 @@
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
- List<GenericDocument> documents = request.getDocuments();
+ List<GenericDocument> documents = request.getGenericDocuments();
List<Bundle> documentBundles = new ArrayList<>(documents.size());
for (int i = 0; i < documents.size(); i++) {
documentBundles.add(documents.get(i).getBundle());
@@ -327,7 +327,7 @@
mDatabaseName,
request.getNamespace(),
new ArrayList<>(request.getUris()),
- request.getProjectionsVisibleToPackagesInternal(),
+ request.getProjectionsInternal(),
mUserId,
new IAppSearchBatchResultCallback.Stub() {
public void onResult(AppSearchBatchResult result) {
@@ -423,7 +423,7 @@
* @return The search result of performing this operation.
*/
@NonNull
- public SearchResults query(
+ public SearchResults search(
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec,
@NonNull @CallbackExecutor Executor executor) {
@@ -441,7 +441,7 @@
* <p>A usage report represents an event in which a user interacted with or viewed a document.
*
* <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
- * metrics for that particular document. These metrics are used for ordering {@link #query}
+ * metrics for that particular document. These metrics are used for ordering {@link #search}
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
* {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
*
@@ -494,7 +494,7 @@
* {@link Throwable} if an unexpected internal error occurred in AppSearch
* service.
*/
- public void removeByUri(
+ public void remove(
@NonNull RemoveByUriRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull BatchResultCallback<String, Void> callback) {
@@ -523,7 +523,8 @@
/**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
* match the {@code queryExpression} in given namespaces and schemaTypes which is set via
- * {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
+ * {@link SearchSpec.Builder#addFilterNamespaces} and
+ * {@link SearchSpec.Builder#addFilterSchemas}.
*
* <p> An empty {@code queryExpression} matches all documents.
*
@@ -539,7 +540,8 @@
* the operation succeeds, the callback will be invoked with
* {@code null}.
*/
- public void removeByQuery(@NonNull String queryExpression,
+ public void remove(
+ @NonNull String queryExpression,
@NonNull SearchSpec searchSpec,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<Void>> callback) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
index 6bb8554..8651834 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -134,7 +134,7 @@
* @return The search result of performing this operation.
*/
@NonNull
- public SearchResults query(
+ public SearchResults search(
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec,
@NonNull @CallbackExecutor Executor executor) {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 2e00ff2..e94b3b2 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -141,10 +141,6 @@
}
/** Adds a property to the given type. */
- // TODO(b/171360120): MissingGetterMatchingBuilder expects a method called getPropertys, but
- // we provide the (correct) method getProperties. Once the bug referenced in this TODO is
- // fixed, remove this SuppressLint.
- @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 2f02808..4c11514 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -22,6 +22,7 @@
import android.annotation.SuppressLint;
import android.app.appsearch.util.BundleUtil;
import android.os.Bundle;
+import android.os.Parcelable;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -37,9 +38,9 @@
*
* <p>Documents are constructed via {@link GenericDocument.Builder}.
*
- * @see AppSearchSession#putDocuments
+ * @see AppSearchSession#put
* @see AppSearchSession#getByUri
- * @see AppSearchSession#query
+ * @see AppSearchSession#search
*/
public class GenericDocument {
private static final String TAG = "AppSearchGenericDocumen";
@@ -210,7 +211,7 @@
Object property = mProperties.get(key);
if (property instanceof ArrayList) {
return getPropertyBytesArray(key);
- } else if (property instanceof Bundle[]) {
+ } else if (property instanceof Parcelable[]) {
return getPropertyDocumentArray(key);
}
return property;
@@ -436,7 +437,7 @@
@Nullable
public GenericDocument[] getPropertyDocumentArray(@NonNull String key) {
Preconditions.checkNotNull(key);
- Bundle[] bundles = getAndCastPropertyArray(key, Bundle[].class);
+ Parcelable[] bundles = getAndCastPropertyArray(key, Parcelable[].class);
if (bundles == null || bundles.length == 0) {
return null;
}
@@ -446,7 +447,18 @@
Log.e(TAG, "The inner bundle is null at " + i + ", for key: " + key);
continue;
}
- documents[i] = new GenericDocument(bundles[i]);
+ if (!(bundles[i] instanceof Bundle)) {
+ Log.e(
+ TAG,
+ "The inner element at "
+ + i
+ + " is a "
+ + bundles[i].getClass()
+ + ", not a Bundle for key: "
+ + key);
+ continue;
+ }
+ documents[i] = new GenericDocument((Bundle) bundles[i]);
}
return documents;
}
@@ -574,8 +586,8 @@
* @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
* schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
* inserting a document of this {@code schemaType} into the AppSearch index using {@link
- * AppSearchSession#putDocuments}. Otherwise, the document will be rejected by {@link
- * AppSearchSession#putDocuments}.
+ * AppSearchSession#put}. Otherwise, the document will be rejected by {@link
+ * AppSearchSession#put}.
*/
@SuppressWarnings("unchecked")
public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -817,7 +829,7 @@
private void putInPropertyBundle(@NonNull String key, @NonNull GenericDocument[] values) {
validateRepeatedPropertyLength(key, values.length);
- Bundle[] documentBundles = new Bundle[values.length];
+ Parcelable[] documentBundles = new Parcelable[values.length];
for (int i = 0; i < values.length; i++) {
if (values[i] == null) {
throw new IllegalArgumentException("The document at " + i + " is null.");
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 38e0046..0fcf061 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -96,7 +96,7 @@
* @hide
*/
@NonNull
- public Map<String, List<String>> getProjectionsVisibleToPackagesInternal() {
+ public Map<String, List<String>> getProjectionsInternal() {
return mTypePropertyPathsMap;
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
index 0f141d6..05b2128 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
@@ -16,8 +16,8 @@
package android.app.appsearch;
+
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
import com.android.internal.util.Preconditions;
@@ -41,7 +41,7 @@
/** Returns the documents that are part of this request. */
@NonNull
- public List<GenericDocument> getDocuments() {
+ public List<GenericDocument> getGenericDocuments() {
return Collections.unmodifiableList(mDocuments);
}
@@ -55,17 +55,15 @@
private boolean mBuilt = false;
/** Adds one or more {@link GenericDocument} objects to the request. */
- @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull
- public Builder addGenericDocument(@NonNull GenericDocument... documents) {
+ public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
Preconditions.checkNotNull(documents);
- return addGenericDocument(Arrays.asList(documents));
+ return addGenericDocuments(Arrays.asList(documents));
}
/** Adds a collection of {@link GenericDocument} objects to the request. */
- @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull
- public Builder addGenericDocument(
+ public Builder addGenericDocuments(
@NonNull Collection<? extends GenericDocument> documents) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(documents);
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 a04da34..2104198d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -29,7 +29,7 @@
/**
* Encapsulates a request to remove documents by namespace and URI.
*
- * @see AppSearchSession#removeByUri
+ * @see AppSearchSession#remove
*/
public final class RemoveByUriRequest {
private final String mNamespace;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
index 963062c..f72f8e1 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -19,7 +19,6 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.IllegalSearchSpecException;
import android.os.Bundle;
import android.util.ArrayMap;
@@ -49,7 +48,7 @@
public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
- static final String SCHEMA_TYPE_FIELD = "schemaType";
+ static final String SCHEMA_FIELD = "schema";
static final String NAMESPACE_FIELD = "namespace";
static final String PACKAGE_NAME_FIELD = "packageName";
static final String NUM_PER_PAGE_FIELD = "numPerPage";
@@ -171,12 +170,12 @@
* <p>If empty, the query will search over all schema types.
*/
@NonNull
- public List<String> getSchemaTypes() {
- List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
- if (schemaTypes == null) {
+ public List<String> getFilterSchemas() {
+ List<String> schemas = mBundle.getStringArrayList(SCHEMA_FIELD);
+ if (schemas == null) {
return Collections.emptyList();
}
- return Collections.unmodifiableList(schemaTypes);
+ return Collections.unmodifiableList(schemas);
}
/**
@@ -185,7 +184,7 @@
* <p>If empty, the query will search over all namespaces.
*/
@NonNull
- public List<String> getNamespaces() {
+ public List<String> getFilterNamespaces() {
List<String> namespaces = mBundle.getStringArrayList(NAMESPACE_FIELD);
if (namespaces == null) {
return Collections.emptyList();
@@ -209,24 +208,6 @@
return Collections.unmodifiableList(packageNames);
}
- /**
- * Returns the list of package names to search over.
- *
- * <p>If unset, the query will search over all packages that the caller has access to. If
- * package names are specified which caller doesn't have access to, then those package names
- * will be ignored.
- *
- * @hide
- */
- @NonNull
- public List<String> getPackageNames() {
- List<String> packageNames = mBundle.getStringArrayList(PACKAGE_NAME_FIELD);
- if (packageNames == null) {
- return Collections.emptyList();
- }
- return Collections.unmodifiableList(packageNames);
- }
-
/** Returns the number of results per page in the result set. */
public int getResultCountPerPage() {
return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
@@ -270,11 +251,10 @@
@NonNull
public Map<String, List<String>> getProjections() {
Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
- Set<String> schemaTypes = typePropertyPathsBundle.keySet();
- Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemaTypes.size());
- for (String schemaType : schemaTypes) {
- typePropertyPathsMap.put(
- schemaType, typePropertyPathsBundle.getStringArrayList(schemaType));
+ Set<String> schemas = typePropertyPathsBundle.keySet();
+ Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
+ for (String schema : schemas) {
+ typePropertyPathsMap.put(schema, typePropertyPathsBundle.getStringArrayList(schema));
}
return typePropertyPathsMap;
}
@@ -283,7 +263,7 @@
public static final class Builder {
private final Bundle mBundle;
- private final ArrayList<String> mSchemaTypes = new ArrayList<>();
+ private final ArrayList<String> mSchemas = new ArrayList<>();
private final ArrayList<String> mNamespaces = new ArrayList<>();
private final ArrayList<String> mPackageNames = new ArrayList<>();
private final Bundle mProjectionTypePropertyMasks = new Bundle();
@@ -312,10 +292,10 @@
* <p>If unset, the query will search over all schema types.
*/
@NonNull
- public Builder addSchemaType(@NonNull String... schemaTypes) {
- Preconditions.checkNotNull(schemaTypes);
+ public Builder addFilterSchemas(@NonNull String... schemas) {
+ Preconditions.checkNotNull(schemas);
Preconditions.checkState(!mBuilt, "Builder has already been used");
- return addSchemaType(Arrays.asList(schemaTypes));
+ return addFilterSchemas(Arrays.asList(schemas));
}
/**
@@ -325,10 +305,10 @@
* <p>If unset, the query will search over all schema types.
*/
@NonNull
- public Builder addSchemaType(@NonNull Collection<String> schemaTypes) {
- Preconditions.checkNotNull(schemaTypes);
+ public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
+ Preconditions.checkNotNull(schemas);
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mSchemaTypes.addAll(schemaTypes);
+ mSchemas.addAll(schemas);
return this;
}
@@ -339,10 +319,10 @@
* <p>If unset, the query will search over all namespaces.
*/
@NonNull
- public Builder addNamespace(@NonNull String... namespaces) {
+ public Builder addFilterNamespaces(@NonNull String... namespaces) {
Preconditions.checkNotNull(namespaces);
Preconditions.checkState(!mBuilt, "Builder has already been used");
- return addNamespace(Arrays.asList(namespaces));
+ return addFilterNamespaces(Arrays.asList(namespaces));
}
/**
@@ -352,7 +332,7 @@
* <p>If unset, the query will search over all namespaces.
*/
@NonNull
- public Builder addNamespace(@NonNull Collection<String> namespaces) {
+ public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
Preconditions.checkNotNull(namespaces);
Preconditions.checkState(!mBuilt, "Builder has already been used");
mNamespaces.addAll(namespaces);
@@ -367,9 +347,6 @@
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
- // Getter is called "getFilterPackageNames" (as opposed to the suggested
- // "getFilterPackageNameses")
- @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder addFilterPackageNames(@NonNull String... packageNames) {
Preconditions.checkNotNull(packageNames);
@@ -385,9 +362,6 @@
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
- // Getter is called "getFilterPackageNames" (as opposed to the suggested
- // "getFilterPackageNameses")
- @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
Preconditions.checkNotNull(packageNames);
@@ -537,7 +511,7 @@
* type property paths:
*
* <pre>{@code
- * {schemaType: "Email", ["subject", "sender.name", "recipients.name"]}
+ * {schema: "Email", ["subject", "sender.name", "recipients.name"]}
* }</pre>
*
* <p>The above document will be returned as:
@@ -561,9 +535,9 @@
*/
@NonNull
public SearchSpec.Builder addProjection(
- @NonNull String schemaType, @NonNull String... propertyPaths) {
+ @NonNull String schema, @NonNull String... propertyPaths) {
Preconditions.checkNotNull(propertyPaths);
- return addProjection(schemaType, Arrays.asList(propertyPaths));
+ return addProjection(schema, Arrays.asList(propertyPaths));
}
/**
@@ -583,16 +557,16 @@
*/
@NonNull
public SearchSpec.Builder addProjection(
- @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
+ @NonNull String schema, @NonNull Collection<String> propertyPaths) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(schemaType);
+ Preconditions.checkNotNull(schema);
Preconditions.checkNotNull(propertyPaths);
ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
for (String propertyPath : propertyPaths) {
Preconditions.checkNotNull(propertyPath);
propertyPathsArrayList.add(propertyPath);
}
- mProjectionTypePropertyMasks.putStringArrayList(schemaType, propertyPathsArrayList);
+ mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
return this;
}
@@ -608,7 +582,7 @@
throw new IllegalSearchSpecException("Missing termMatchType field.");
}
mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
- mBundle.putStringArrayList(SCHEMA_TYPE_FIELD, mSchemaTypes);
+ mBundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
mBundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
mBuilt = true;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index 1486df3..2caa94a5e 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -126,9 +126,9 @@
* <p>Any documents of these types will be visible on system UI surfaces by default.
*/
@NonNull
- public Builder addSchema(@NonNull AppSearchSchema... schemas) {
+ public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
Preconditions.checkNotNull(schemas);
- return addSchema(Arrays.asList(schemas));
+ return addSchemas(Arrays.asList(schemas));
}
/**
@@ -137,7 +137,7 @@
* <p>Any documents of these types will be visible on system UI surfaces by default.
*/
@NonNull
- public Builder addSchema(@NonNull Collection<AppSearchSchema> schemas) {
+ public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(schemas);
mSchemas.addAll(schemas);
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 90a6f60..0328d54 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -179,9 +179,9 @@
/** Adds migratedTypes to the list of migrated schema types. */
@NonNull
- public Builder addMigratedType(@NonNull String migratedType) {
+ public Builder addMigratedType(@NonNull Collection<String> migratedTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
+ mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
return this;
}
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 592b8b9..8bff720 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
@@ -195,8 +195,7 @@
mIcingSearchEngineLocked = new IcingSearchEngine(options);
mVisibilityStoreLocked =
- new VisibilityStore(
- this, context, userId, globalQuerierPackage);
+ new VisibilityStore(this, context, userId, globalQuerierPackage);
InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
SchemaProto schemaProto;
@@ -508,8 +507,8 @@
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
- if (!searchSpec.getPackageNames().isEmpty()
- && !searchSpec.getPackageNames().contains(packageName)) {
+ List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+ if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// Client wanted to query over some packages that weren't its own. This isn't
// allowed through local query so we can return early with no results.
return new SearchResultPage(Bundle.EMPTY);
@@ -553,7 +552,7 @@
throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
- Set<String> packageFilters = new ArraySet<>(searchSpec.getPackageNames());
+ Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
Set<String> prefixFilters = new ArraySet<>();
Set<String> allPrefixes = mNamespaceMapLocked.keySet();
if (packageFilters.isEmpty()) {
@@ -573,7 +572,7 @@
// Find which schemas the client is allowed to query over.
Set<String> allowedPrefixedSchemas = new ArraySet<>();
- List<String> schemaFilters = searchSpec.getSchemaTypes();
+ List<String> schemaFilters = searchSpec.getFilterSchemas();
for (String prefix : prefixFilters) {
String packageName = getPackageName(prefix);
@@ -752,8 +751,8 @@
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
- if (!searchSpec.getPackageNames().isEmpty()
- && !searchSpec.getPackageNames().contains(packageName)) {
+ List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+ if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// We're only removing documents within the parameter `packageName`. If we're not
// restricting our remove-query to this package name, then there's nothing for us to
// remove.
@@ -1085,7 +1084,7 @@
Set<String> allowedPrefixedSchemas = new ArraySet<>();
// Add all the schema filters the client specified.
- List<String> schemaFilters = searchSpec.getSchemaTypes();
+ List<String> schemaFilters = searchSpec.getFilterSchemas();
for (int i = 0; i < schemaFilters.size(); i++) {
allowedPrefixedSchemas.add(prefix + schemaFilters.get(i));
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
new file mode 100644
index 0000000..b3f8264
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.external.localstorage;
+
+import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchMigrationHelper;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.SearchResultPage;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaResponse;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import com.android.internal.util.Preconditions;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * An implementation of {@link AppSearchMigrationHelper} which query document and save post-migrated
+ * documents to locally in the app's storage space.
+ */
+class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper {
+ private final AppSearchImpl mAppSearchImpl;
+ private final String mPackageName;
+ private final String mDatabaseName;
+ private final File mFile;
+ private final Map<String, Integer> mCurrentVersionMap;
+ private final Map<String, Integer> mFinalVersionMap;
+
+ AppSearchMigrationHelperImpl(
+ @NonNull AppSearchImpl appSearchImpl,
+ @NonNull Map<String, Integer> currentVersionMap,
+ @NonNull Map<String, Integer> finalVersionMap,
+ @NonNull String packageName,
+ @NonNull String databaseName)
+ throws IOException {
+ mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
+ mCurrentVersionMap = Preconditions.checkNotNull(currentVersionMap);
+ mFinalVersionMap = Preconditions.checkNotNull(finalVersionMap);
+ mPackageName = Preconditions.checkNotNull(packageName);
+ mDatabaseName = Preconditions.checkNotNull(databaseName);
+ mFile = File.createTempFile(/*prefix=*/ "appsearch", /*suffix=*/ null);
+ }
+
+ @Override
+ public void queryAndTransform(
+ @NonNull String schemaType, @NonNull AppSearchMigrationHelper.Transformer migrator)
+ throws Exception {
+ Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
+ int currentVersion = mCurrentVersionMap.get(schemaType);
+ int finalVersion = mFinalVersionMap.get(schemaType);
+ try (FileOutputStream outputStream = new FileOutputStream(mFile)) {
+ // TODO(b/151178558) change the output stream so that we can use it in platform
+ CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
+ SearchResultPage searchResultPage =
+ mAppSearchImpl.query(
+ mPackageName,
+ mDatabaseName,
+ /*queryExpression=*/ "",
+ new SearchSpec.Builder()
+ .addFilterSchemas(schemaType)
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .build());
+ while (!searchResultPage.getResults().isEmpty()) {
+ for (int i = 0; i < searchResultPage.getResults().size(); i++) {
+ GenericDocument newDocument =
+ migrator.transform(
+ currentVersion,
+ finalVersion,
+ searchResultPage.getResults().get(i).getDocument());
+ Bundle bundle = newDocument.getBundle();
+ Parcel parcel = Parcel.obtain();
+ parcel.writeBundle(bundle);
+ byte[] serializedMessage = parcel.marshall();
+ parcel.recycle();
+ codedOutputStream.writeByteArrayNoTag(serializedMessage);
+ }
+ codedOutputStream.flush();
+ searchResultPage = mAppSearchImpl.getNextPage(searchResultPage.getNextPageToken());
+ outputStream.flush();
+ }
+ }
+ }
+
+ /**
+ * Reads {@link GenericDocument} from the temperate file and saves them to AppSearch.
+ *
+ * <p>This method should be only called once.
+ *
+ * @return the {@link AppSearchBatchResult} for migration documents.
+ */
+ @NonNull
+ public SetSchemaResponse readAndPutDocuments(SetSchemaResponse.Builder responseBuilder)
+ throws IOException, AppSearchException {
+ Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
+ try (InputStream inputStream = new FileInputStream(mFile)) {
+ CodedInputStream codedInputStream = CodedInputStream.newInstance(inputStream);
+ while (!codedInputStream.isAtEnd()) {
+ GenericDocument document = readDocumentFromInputStream(codedInputStream);
+ try {
+ mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
+ } catch (Throwable t) {
+ responseBuilder.setFailure(
+ document.getSchemaType(),
+ document.getNamespace(),
+ document.getUri(),
+ throwableToFailedResult(t));
+ }
+ }
+ mAppSearchImpl.persistToDisk();
+ return responseBuilder.build();
+ } finally {
+ mFile.delete();
+ }
+ }
+
+ void deleteTempFile() {
+ mFile.delete();
+ }
+
+ /**
+ * Reads {@link GenericDocument} from given {@link CodedInputStream}.
+ *
+ * @param codedInputStream The codedInputStream to read from
+ * @throws IOException on File operation error.
+ */
+ @NonNull
+ private static GenericDocument readDocumentFromInputStream(
+ @NonNull CodedInputStream codedInputStream) throws IOException {
+ byte[] serializedMessage = codedInputStream.readByteArray();
+
+ Parcel parcel = Parcel.obtain();
+ parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
+ parcel.setDataPosition(0);
+ Bundle bundle = parcel.readBundle();
+ parcel.recycle();
+
+ return new GenericDocument(bundle);
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
index 07d50ae..3b5e275 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
@@ -40,8 +40,8 @@
Preconditions.checkNotNull(spec);
SearchSpecProto.Builder protoBuilder =
SearchSpecProto.newBuilder()
- .addAllSchemaTypeFilters(spec.getSchemaTypes())
- .addAllNamespaceFilters(spec.getNamespaces());
+ .addAllSchemaTypeFilters(spec.getFilterSchemas())
+ .addAllNamespaceFilters(spec.getFilterNamespaces());
@SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 2774181..12699b7 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I3fd4c96bf775c2539d744c416cdbf1d3c9544f03
+Ibe06fb9c574c8718191f833bb042fa10c300e4e2
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
index f8d0d80..afa633a 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
@@ -102,10 +102,10 @@
@Override
@NonNull
- public ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
+ public ListenableFuture<AppSearchBatchResult<String, Void>> put(
@NonNull PutDocumentsRequest request) {
SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
- mAppSearchSession.putDocuments(
+ mAppSearchSession.put(
request, mExecutor, new BatchResultCallbackAdapter<>(future));
return future;
}
@@ -122,10 +122,10 @@
@Override
@NonNull
- public SearchResultsShim query(
+ public SearchResultsShim search(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults =
- mAppSearchSession.query(queryExpression, searchSpec, mExecutor);
+ mAppSearchSession.search(queryExpression, searchSpec, mExecutor);
return new SearchResultsShimImpl(searchResults, mExecutor);
}
@@ -139,19 +139,19 @@
@Override
@NonNull
- public ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
+ public ListenableFuture<AppSearchBatchResult<String, Void>> remove(
@NonNull RemoveByUriRequest request) {
SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
- mAppSearchSession.removeByUri(request, mExecutor, new BatchResultCallbackAdapter<>(future));
+ mAppSearchSession.remove(request, mExecutor, new BatchResultCallbackAdapter<>(future));
return future;
}
@Override
@NonNull
- public ListenableFuture<Void> removeByQuery(
+ public ListenableFuture<Void> remove(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
- mAppSearchSession.removeByQuery(queryExpression, searchSpec, mExecutor, future::set);
+ mAppSearchSession.remove(queryExpression, searchSpec, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
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 39ca687..eb1623e 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
@@ -67,10 +67,10 @@
@NonNull
@Override
- public SearchResultsShim query(
+ public SearchResultsShim search(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults =
- mGlobalSearchSession.query(queryExpression, searchSpec, mExecutor);
+ mGlobalSearchSession.search(queryExpression, searchSpec, mExecutor);
return new SearchResultsShimImpl(searchResults, mExecutor);
}
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index e8ea6ef..8e62c0e 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -33,7 +33,7 @@
public interface AppSearchSessionShim extends Closeable {
/**
- * Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
+ * Sets the schema that will be used by documents provided to the {@link #put} method.
*
* <p>The schema provided here is compared to the stored copy of the schema previously supplied
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -143,8 +143,7 @@
* they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
*/
@NonNull
- ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
- @NonNull PutDocumentsRequest request);
+ ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request);
/**
* Retrieves {@link GenericDocument}s by URI.
@@ -161,7 +160,7 @@
@NonNull GetByUriRequest request);
/**
- * Searches a document based on a given query string.
+ * Searches for documents based on a given query string.
*
* <p>Currently we support following features in the raw query format:
*
@@ -200,7 +199,7 @@
* @return The search result of performing this operation.
*/
@NonNull
- SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+ SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/**
* Reports usage of a particular document by URI and namespace.
@@ -208,7 +207,7 @@
* <p>A usage report represents an event in which a user interacted with or viewed a document.
*
* <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
- * metrics for that particular document. These metrics are used for ordering {@link #query}
+ * metrics for that particular document. These metrics are used for ordering {@link #search}
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
* SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
*
@@ -231,13 +230,13 @@
* {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
- ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
+ ListenableFuture<AppSearchBatchResult<String, Void>> remove(
@NonNull RemoveByUriRequest request);
/**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
* match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
- * SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
+ * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
*
* <p>An empty {@code queryExpression} matches all documents.
*
@@ -251,8 +250,7 @@
* @return The pending result of performing this operation.
*/
@NonNull
- ListenableFuture<Void> removeByQuery(
- @NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+ ListenableFuture<Void> remove(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/**
* Flush all schema and document updates, additions, and deletes to disk if possible.
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
index cd867a4..b96f99e 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
@@ -23,7 +23,7 @@
/**
* This class provides global access to the centralized AppSearch index maintained by the system.
*
- * <p>Apps can retrieve indexed documents through the query API.
+ * <p>Apps can retrieve indexed documents through the {@link #search} API.
*/
public interface GlobalSearchSessionShim extends Closeable {
/**
@@ -66,7 +66,7 @@
* @return The search result of performing this operation.
*/
@NonNull
- SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+ SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/** Closes the {@link GlobalSearchSessionShim}. */
@Override
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 6eb44a7..930415f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -593,15 +593,6 @@
}
/**
- * @see JobInfo.Builder#setExpedited(boolean)
- * @deprecated Use {@link #isExpedited()} instead
- */
- @Deprecated
- public boolean isForegroundJob() {
- return (flags & FLAG_EXPEDITED) != 0;
- }
-
- /**
* @see JobInfo.Builder#setImportantWhileForeground(boolean)
*/
public boolean isImportantWhileForeground() {
@@ -1503,20 +1494,6 @@
}
/**
- * @deprecated Use {@link #setExpedited(boolean)} instead.
- */
- @Deprecated
- @NonNull
- public Builder setForeground(boolean foreground) {
- if (foreground) {
- mFlags |= FLAG_EXPEDITED;
- } else {
- mFlags &= (~FLAG_EXPEDITED);
- }
- return this;
- }
-
- /**
* Setting this to true indicates that this job is important while the scheduling app
* is in the foreground or on the temporary whitelist for background restrictions.
* This means that the system will relax doze restrictions on this job during this time.
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index ab87222..283e933 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -74,6 +74,27 @@
}
/**
+ * Allow the temp allowlist behavior, plus allow foreground service start from background.
+ */
+ public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0;
+ /**
+ * Only allow the temp allowlist behavior, not allow foreground service start from
+ * background.
+ */
+ public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1;
+
+ /**
+ * The list of temp allowlist types.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "TEMPORARY_ALLOW_TYPE_" }, value = {
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TempAllowListType {}
+
+ /**
* @hide
*/
public PowerWhitelistManager(@NonNull Context context) {
diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
index 4441643..e045b0f 100644
--- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
@@ -16,7 +16,7 @@
package com.android.server;
-import android.app.BroadcastOptions;
+import android.os.PowerWhitelistManager.TempAllowListType;
import com.android.server.deviceidle.IDeviceIdleConstraint;
@@ -39,12 +39,12 @@
* allowlist.
* @param uid
* @param duration duration in milliseconds
- * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+ * @param type temp allowlist type defined at {@link TempAllowListType}
* @param sync
* @param reason
*/
void addPowerSaveTempWhitelistAppDirect(int uid, long duration,
- @BroadcastOptions.TempAllowListType int type, boolean sync,
+ @TempAllowListType int type, boolean sync,
String reason);
// duration in milliseconds
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index c9427e9..8f7f705 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -22,7 +22,6 @@
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.BroadcastOptions;
-import android.app.BroadcastOptions.TempAllowListType;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -57,6 +56,7 @@
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
+import android.os.PowerWhitelistManager.TempAllowListType;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -3879,7 +3879,7 @@
* @param adding true to add to temp allowlist, false to remove from temp allowlist.
* @param durationMs duration in milliseconds to add to temp allowlist, only valid when
* param adding is true.
- * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+ * @param type temp allowlist type defined at {@link TempAllowListType}
*/
private void updateTempWhitelistAppIdsLocked(int uid, boolean adding, long durationMs,
@TempAllowListType int type) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index e05f0b0..18856f7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -62,6 +62,7 @@
CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
+ // Try to give higher priority types lower values.
static final int WORK_TYPE_NONE = 0;
static final int WORK_TYPE_TOP = 1 << 0;
static final int WORK_TYPE_BG = 1 << 1;
@@ -520,6 +521,120 @@
}
}
+ void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
+ @WorkType final int workType) {
+ mWorkCountTracker.onJobFinished(workType);
+ mRunningJobs.remove(jobStatus);
+ final List<JobStatus> pendingJobs = mService.mPendingJobs;
+ if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
+ updateCounterConfigLocked();
+ // Preemption case needs special care.
+ updateNonRunningPriorities(pendingJobs, false);
+
+ JobStatus highestPriorityJob = null;
+ int highPriWorkType = workType;
+ JobStatus backupJob = null;
+ int backupWorkType = WORK_TYPE_NONE;
+ for (int i = 0; i < pendingJobs.size(); i++) {
+ final JobStatus nextPending = pendingJobs.get(i);
+
+ if (mRunningJobs.contains(nextPending)) {
+ continue;
+ }
+
+ if (worker.getPreferredUid() != nextPending.getUid()) {
+ if (backupJob == null) {
+ int workAsType =
+ mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ if (workAsType != WORK_TYPE_NONE) {
+ backupJob = nextPending;
+ backupWorkType = workAsType;
+ }
+ }
+ continue;
+ }
+
+ if (highestPriorityJob == null
+ || highestPriorityJob.lastEvaluatedPriority
+ < nextPending.lastEvaluatedPriority) {
+ highestPriorityJob = nextPending;
+ } else {
+ continue;
+ }
+
+ // In this path, we pre-empted an existing job. We don't fully care about the
+ // reserved slots. We should just run the highest priority job we can find,
+ // though it would be ideal to use an available WorkType slot instead of
+ // overloading slots.
+ final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ if (workAsType == WORK_TYPE_NONE) {
+ // Just use the preempted job's work type since this new one is technically
+ // replacing it anyway.
+ highPriWorkType = workType;
+ } else {
+ highPriWorkType = workAsType;
+ }
+ }
+ if (highestPriorityJob != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Running job " + jobStatus + " as preemption");
+ }
+ mWorkCountTracker.stageJob(highPriWorkType);
+ startJobLocked(worker, highestPriorityJob, highPriWorkType);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid());
+ }
+ worker.clearPreferredUid();
+ if (backupJob != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Running job " + jobStatus + " instead");
+ }
+ mWorkCountTracker.stageJob(backupWorkType);
+ startJobLocked(worker, backupJob, backupWorkType);
+ }
+ }
+ } else if (pendingJobs.size() > 0) {
+ updateCounterConfigLocked();
+ updateNonRunningPriorities(pendingJobs, false);
+
+ // This slot is now free and we have pending jobs. Start the highest priority job we
+ // find.
+ JobStatus highestPriorityJob = null;
+ int highPriWorkType = workType;
+ for (int i = 0; i < pendingJobs.size(); i++) {
+ final JobStatus nextPending = pendingJobs.get(i);
+
+ if (mRunningJobs.contains(nextPending)) {
+ continue;
+ }
+
+ final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ if (workAsType == WORK_TYPE_NONE) {
+ continue;
+ }
+ if (highestPriorityJob == null
+ || highestPriorityJob.lastEvaluatedPriority
+ < nextPending.lastEvaluatedPriority) {
+ highestPriorityJob = nextPending;
+ highPriWorkType = workAsType;
+ }
+ }
+
+ if (highestPriorityJob != null) {
+ // This slot is free, and we haven't yet hit the limit on
+ // concurrent jobs... we can just throw the job in to here.
+ if (DEBUG) {
+ Slog.d(TAG, "About to run job: " + jobStatus);
+ }
+ mWorkCountTracker.stageJob(highPriWorkType);
+ startJobLocked(worker, highestPriorityJob, highPriWorkType);
+ }
+ }
+
+ noteConcurrency();
+ }
+
@GuardedBy("mLock")
private String printPendingQueueLocked() {
StringBuilder s = new StringBuilder("Pending queue: ");
@@ -665,9 +780,18 @@
WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal,
List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) {
mConfigIdentifier = configIdentifier;
- mDefaultMaxTotal = mMaxTotal = defaultMaxTotal;
+ mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, MAX_JOB_CONTEXTS_COUNT);
+ int numReserved = 0;
for (int i = defaultMin.size() - 1; i >= 0; --i) {
mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second);
+ numReserved += defaultMin.get(i).second;
+ }
+ if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
+ // We only create new configs on boot, so this should trigger during development
+ // (before the code gets checked in), so this makes sure the hard-coded defaults
+ // make sense. DeviceConfig values will be handled gracefully in update().
+ throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
+ + " min=" + defaultMin + " max=" + defaultMax);
}
for (int i = defaultMax.size() - 1; i >= 0; --i) {
mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second);
@@ -782,15 +906,10 @@
mNumUnspecialized = mConfigMaxTotal;
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
- mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP);
- mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG);
- calculateUnspecializedRemaining();
- }
-
- private void calculateUnspecializedRemaining() {
- mNumUnspecializedRemaining = mNumUnspecialized;
+ mNumUnspecializedRemaining = mConfigMaxTotal;
for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
- mNumUnspecializedRemaining -= mNumRunningJobs.valueAt(i);
+ mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
+ mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
}
}
@@ -851,7 +970,25 @@
if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) {
// We've run all jobs for this type. Let another type use it now.
mNumActuallyReservedSlots.put(workType, numRemainingForType);
- mNumUnspecializedRemaining++;
+ int assignWorkType = WORK_TYPE_NONE;
+ for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
+ int wt = mNumActuallyReservedSlots.keyAt(i);
+ if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
+ // Try to give this slot to the highest priority one within its limits.
+ int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
+ + mNumPendingJobs.get(wt);
+ if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
+ && total > mNumActuallyReservedSlots.valueAt(i)) {
+ assignWorkType = wt;
+ }
+ }
+ }
+ if (assignWorkType != WORK_TYPE_NONE) {
+ mNumActuallyReservedSlots.put(assignWorkType,
+ mNumActuallyReservedSlots.get(assignWorkType) + 1);
+ } else {
+ mNumUnspecializedRemaining++;
+ }
}
}
@@ -867,30 +1004,60 @@
}
}
+ void onJobFinished(@WorkType int workType) {
+ final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1;
+ if (newNumRunningJobs < 0) {
+ // We are in a bad state. We will eventually recover when the pending list is
+ // regenerated.
+ Slog.e(TAG, "# running jobs for " + workType + " went negative.");
+ return;
+ }
+ mNumRunningJobs.put(workType, newNumRunningJobs);
+ maybeAdjustReservations(workType);
+ }
+
void onCountDone() {
// Calculate how many slots to reserve for each work type. "Unspecialized" slots will
// be reserved for higher importance types first (ie. top before bg).
mNumUnspecialized = mConfigMaxTotal;
final int numTop = mNumRunningJobs.get(WORK_TYPE_TOP)
+ mNumPendingJobs.get(WORK_TYPE_TOP);
- final int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
+ int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop);
mNumUnspecialized -= resTop;
final int numBg = mNumRunningJobs.get(WORK_TYPE_BG) + mNumPendingJobs.get(WORK_TYPE_BG);
- final int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
+ int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
mNumUnspecialized -= resBg;
- calculateUnspecializedRemaining();
+
+ mNumUnspecializedRemaining = mNumUnspecialized;
+ // Account for already running jobs after we've assigned the minimum number of slots.
+ int unspecializedAssigned;
+ int extraRunning = (mNumRunningJobs.get(WORK_TYPE_TOP) - resTop);
+ if (extraRunning > 0) {
+ unspecializedAssigned = Math.max(0,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP) - resTop,
+ extraRunning));
+ resTop += unspecializedAssigned;
+ mNumUnspecializedRemaining -= extraRunning;
+ }
+ extraRunning = (mNumRunningJobs.get(WORK_TYPE_BG) - resBg);
+ if (extraRunning > 0) {
+ unspecializedAssigned = Math.max(0,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG) - resBg, extraRunning));
+ resBg += unspecializedAssigned;
+ mNumUnspecializedRemaining -= extraRunning;
+ }
// Assign remaining unspecialized based on ranking.
- int unspecializedAssigned = Math.max(0,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
- Math.min(mNumUnspecializedRemaining, numTop - resTop)));
+ unspecializedAssigned = Math.max(0,
+ Math.min(mNumUnspecializedRemaining,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop));
mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned;
unspecializedAssigned = Math.max(0,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
- Math.min(mNumUnspecializedRemaining, numBg - resBg)));
+ Math.min(mNumUnspecializedRemaining,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned;
}
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 ba78bda..7ce867c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1426,8 +1426,8 @@
// Create the "runners".
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mActiveServices.add(
- new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
- getContext().getMainLooper()));
+ new JobServiceContext(this, mConcurrencyManager, mBatteryStats,
+ mJobPackageTracker, getContext().getMainLooper()));
}
// Attach jobs to their controllers.
mJobs.forEachJob((job) -> {
@@ -1710,9 +1710,6 @@
if (DEBUG) {
Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
}
- // We still want to check for jobs to execute, because this job may have
- // scheduled a new job under the same job id, and now we can run it.
- mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
return;
}
@@ -1734,7 +1731,6 @@
}
jobStatus.unprepareLocked();
reportActiveLocked();
- mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
}
// StateChangedListener implementations.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 247b421..d15bae0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -107,6 +107,7 @@
private final Handler mCallbackHandler;
/** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
private final JobCompletedListener mCompletedListener;
+ private final JobConcurrencyManager mJobConcurrencyManager;
/** Used for service binding, etc. */
private final Context mContext;
private final Object mLock;
@@ -183,13 +184,14 @@
}
}
- JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
- JobPackageTracker tracker, Looper looper) {
+ JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
+ IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) {
mContext = service.getContext();
mLock = service.getLock();
mBatteryStats = batteryStats;
mJobPackageTracker = tracker;
mCallbackHandler = new JobServiceHandler(looper);
+ mJobConcurrencyManager = concurrencyManager;
mCompletedListener = service;
mAvailable = true;
mVerb = VERB_FINISHED;
@@ -835,6 +837,7 @@
if (mWakeLock != null) {
mWakeLock.release();
}
+ final int workType = mRunningJobWorkType;
mContext.unbindService(JobServiceContext.this);
mWakeLock = null;
mRunningJob = null;
@@ -847,6 +850,7 @@
mAvailable = true;
removeOpTimeOutLocked();
mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
+ mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
private void applyStoppedReasonLocked(String reason) {
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 5f13a5c..f85e30d 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -70,6 +70,7 @@
private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
+ private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
/**
* Change the system dialer package name if a package name was specified,
@@ -220,6 +221,9 @@
case COMMAND_CLEANUP_STUCK_CALLS:
runCleanupStuckCalls();
break;
+ case COMMAND_RESET_CAR_MODE:
+ runResetCarMode();
+ break;
case COMMAND_SET_DEFAULT_DIALER:
runSetDefaultDialer();
break;
@@ -345,6 +349,10 @@
mTelecomService.cleanupStuckCalls();
}
+ private void runResetCarMode() throws RemoteException {
+ mTelecomService.resetCarMode();
+ }
+
private void runSetDefaultDialer() throws RemoteException {
String packageName = nextArg();
if ("default".equals(packageName)) packageName = null;
diff --git a/core/api/current.txt b/core/api/current.txt
index 6eb3ff8..a945889 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -157,7 +157,7 @@
field public static final String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
field public static final String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
- field public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
+ field @Deprecated public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
@@ -722,6 +722,7 @@
field public static final int gwpAsanMode = 16844310; // 0x1010616
field public static final int hand_hour = 16843011; // 0x1010103
field public static final int hand_minute = 16843012; // 0x1010104
+ field public static final int hand_second = 16844323; // 0x1010623
field public static final int handle = 16843354; // 0x101025a
field public static final int handleProfiling = 16842786; // 0x1010022
field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
@@ -963,6 +964,7 @@
field public static final int measureWithLargestChild = 16843476; // 0x10102d4
field public static final int mediaRouteButtonStyle = 16843693; // 0x10103ad
field public static final int mediaRouteTypes = 16843694; // 0x10103ae
+ field public static final int memtagMode = 16844324; // 0x1010624
field public static final int menuCategory = 16843230; // 0x10101de
field public static final int mimeGroup = 16844309; // 0x1010615
field public static final int mimeType = 16842790; // 0x1010026
@@ -986,6 +988,7 @@
field public static final int multiArch = 16843918; // 0x101048e
field public static final int multiprocess = 16842771; // 0x1010013
field public static final int name = 16842755; // 0x1010003
+ field public static final int nativeHeapZeroInit = 16844325; // 0x1010625
field public static final int navigationBarColor = 16843858; // 0x1010452
field public static final int navigationBarDividerColor = 16844141; // 0x101056d
field public static final int navigationContentDescription = 16843969; // 0x10104c1
@@ -1608,6 +1611,8 @@
field public static final int windowAnimationStyle = 16842926; // 0x10100ae
field public static final int windowBackground = 16842836; // 0x1010054
field public static final int windowBackgroundFallback = 16844035; // 0x1010503
+ field public static final int windowBlurBehindEnabled = 16844316; // 0x101061c
+ field public static final int windowBlurBehindRadius = 16844315; // 0x101061b
field public static final int windowClipToOutline = 16843947; // 0x10104ab
field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -7311,6 +7316,7 @@
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
+ field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1
field public static final int WIPE_EUICC = 4; // 0x4
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -7464,6 +7470,7 @@
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
method public int describeContents();
+ method public int getReason();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
}
@@ -7742,7 +7749,6 @@
method public long getTriggerContentUpdateDelay();
method @Nullable public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
method public boolean isExpedited();
- method @Deprecated public boolean isForegroundJob();
method public boolean isImportantWhileForeground();
method public boolean isPeriodic();
method public boolean isPersisted();
@@ -7775,7 +7781,6 @@
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long, long);
method @NonNull public android.app.job.JobInfo.Builder setExpedited(boolean);
method public android.app.job.JobInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
- method @Deprecated @NonNull public android.app.job.JobInfo.Builder setForeground(boolean);
method @Deprecated public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -9279,7 +9284,7 @@
method public boolean getIncludeTxPowerLevel();
method public android.util.SparseArray<byte[]> getManufacturerSpecificData();
method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
- method @Nullable public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
+ method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
method public java.util.List<android.os.ParcelUuid> getServiceUuids();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
@@ -11638,6 +11643,8 @@
method public void dump(android.util.Printer, String);
method public static CharSequence getCategoryTitle(android.content.Context, int);
method public int getGwpAsanMode();
+ method public int getMemtagMode();
+ method @Nullable public Boolean isNativeHeapZeroInit();
method public boolean isProfileableByShell();
method public boolean isResourceOverlay();
method public boolean isVirtualPreload();
@@ -11687,6 +11694,10 @@
field public static final int GWP_ASAN_ALWAYS = 1; // 0x1
field public static final int GWP_ASAN_DEFAULT = -1; // 0xffffffff
field public static final int GWP_ASAN_NEVER = 0; // 0x0
+ field public static final int MEMTAG_ASYNC = 1; // 0x1
+ field public static final int MEMTAG_DEFAULT = -1; // 0xffffffff
+ field public static final int MEMTAG_OFF = 0; // 0x0
+ field public static final int MEMTAG_SYNC = 2; // 0x2
field public String appComponentFactory;
field public String backupAgentName;
field public int category;
@@ -12354,6 +12365,8 @@
field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+ field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
+ field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key";
field public static final String FEATURE_LEANBACK = "android.software.leanback";
field public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
field public static final String FEATURE_LIVE_TV = "android.software.live_tv";
@@ -12471,6 +12484,7 @@
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
field public static final int PERMISSION_DENIED = -1; // 0xffffffff
field public static final int PERMISSION_GRANTED = 0; // 0x0
+ field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
field public static final int SIGNATURE_MATCH = 0; // 0x0
field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -16803,6 +16817,18 @@
package android.hardware {
+ public abstract class Battery {
+ ctor public Battery();
+ method @FloatRange(from=-1.0F, to=1.0f) public abstract float getCapacity();
+ method public abstract int getStatus();
+ method public abstract boolean hasBattery();
+ field public static final int STATUS_CHARGING = 2; // 0x2
+ field public static final int STATUS_DISCHARGING = 3; // 0x3
+ field public static final int STATUS_FULL = 5; // 0x5
+ field public static final int STATUS_NOT_CHARGING = 4; // 0x4
+ field public static final int STATUS_UNKNOWN = 1; // 0x1
+ }
+
@Deprecated public class Camera {
method @Deprecated public final void addCallbackBuffer(byte[]);
method @Deprecated public final void autoFocus(android.hardware.Camera.AutoFocusCallback);
@@ -19437,7 +19463,7 @@
public interface LocationListener {
method public default void onFlushComplete(int);
method public void onLocationChanged(@NonNull android.location.Location);
- method public default void onLocationChanged(@NonNull android.location.LocationResult);
+ method public default void onLocationChanged(@NonNull java.util.List<android.location.Location>);
method public default void onProviderDisabled(@NonNull String);
method public default void onProviderEnabled(@NonNull String);
method @Deprecated public default void onStatusChanged(String, int, android.os.Bundle);
@@ -19523,8 +19549,8 @@
field public static final String FUSED_PROVIDER = "fused";
field public static final String GPS_PROVIDER = "gps";
field public static final String KEY_FLUSH_COMPLETE = "flushComplete";
+ field public static final String KEY_LOCATIONS = "locations";
field public static final String KEY_LOCATION_CHANGED = "location";
- field public static final String KEY_LOCATION_RESULT = "locationResult";
field public static final String KEY_PROVIDER_ENABLED = "providerEnabled";
field public static final String KEY_PROXIMITY_ENTERING = "entering";
field @Deprecated public static final String KEY_STATUS_CHANGED = "status";
@@ -19582,18 +19608,6 @@
method @NonNull public android.location.LocationRequest.Builder setQuality(int);
}
- public final class LocationResult implements android.os.Parcelable {
- method @NonNull public java.util.List<android.location.Location> asList();
- method @NonNull public static android.location.LocationResult create(@NonNull android.location.Location);
- method @NonNull public static android.location.LocationResult create(@NonNull java.util.List<android.location.Location>);
- method public int describeContents();
- method @NonNull public android.location.Location get(@IntRange(from=0) int);
- method @NonNull public android.location.Location getLastLocation();
- method @IntRange(from=1) public int size();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.location.LocationResult> CREATOR;
- }
-
public interface OnNmeaMessageListener {
method public void onNmeaMessage(String, long);
}
@@ -19853,6 +19867,10 @@
field public static final int ENCODING_IEC61937 = 13; // 0xd
field public static final int ENCODING_INVALID = 0; // 0x0
field public static final int ENCODING_MP3 = 9; // 0x9
+ field public static final int ENCODING_MPEGH_BL_L3 = 23; // 0x17
+ field public static final int ENCODING_MPEGH_BL_L4 = 24; // 0x18
+ field public static final int ENCODING_MPEGH_LC_L3 = 25; // 0x19
+ field public static final int ENCODING_MPEGH_LC_L4 = 26; // 0x1a
field public static final int ENCODING_OPUS = 20; // 0x14
field public static final int ENCODING_PCM_16BIT = 2; // 0x2
field public static final int ENCODING_PCM_24BIT_PACKED = 21; // 0x15
@@ -20163,7 +20181,7 @@
}
public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
- ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
+ ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method @Deprecated public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
method protected void finalize();
@@ -20224,7 +20242,7 @@
public static class AudioRecord.Builder {
ctor public AudioRecord.Builder();
- method public android.media.AudioRecord build() throws java.lang.UnsupportedOperationException;
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public android.media.AudioRecord build() throws java.lang.UnsupportedOperationException;
method public android.media.AudioRecord.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method @NonNull public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
method public android.media.AudioRecord.Builder setAudioSource(int) throws java.lang.IllegalArgumentException;
@@ -21094,7 +21112,9 @@
public static final class MediaCodecInfo.AudioCapabilities {
method public android.util.Range<java.lang.Integer> getBitrateRange();
+ method @NonNull public android.util.Range<java.lang.Integer>[] getInputChannelCountRanges();
method public int getMaxInputChannelCount();
+ method public int getMinInputChannelCount();
method public android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
method public int[] getSupportedSampleRates();
method public boolean isSampleRateSupported(int);
@@ -31850,6 +31870,10 @@
public final class ImplicitDirectBootViolation extends android.os.strictmode.Violation {
}
+ public final class IncorrectContextUseViolation extends android.os.strictmode.Violation {
+ ctor public IncorrectContextUseViolation(@NonNull String, @NonNull Throwable);
+ }
+
public class InstanceCountViolation extends android.os.strictmode.Violation {
method public long getNumberOfInstances();
}
@@ -34951,6 +34975,55 @@
field public static final String PATH_SETTING_INTENT = "intent";
}
+ public final class SimPhonebookContract {
+ field public static final String AUTHORITY = "com.android.simphonebook";
+ field @NonNull public static final android.net.Uri AUTHORITY_URI;
+ }
+
+ public static final class SimPhonebookContract.ElementaryFiles {
+ method @NonNull public static android.net.Uri getItemUri(int, int);
+ field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-elementary-file";
+ field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
+ field @NonNull public static final android.net.Uri CONTENT_URI;
+ field public static final int EF_ADN = 1; // 0x1
+ field public static final int EF_FDN = 2; // 0x2
+ field public static final int EF_SDN = 3; // 0x3
+ field public static final String EF_TYPE = "ef_type";
+ field public static final int EF_UNKNOWN = 0; // 0x0
+ field public static final String MAX_RECORDS = "max_records";
+ field public static final String NAME_MAX_LENGTH = "name_max_length";
+ field public static final String PHONE_NUMBER_MAX_LENGTH = "phone_number_max_length";
+ field public static final String RECORD_COUNT = "record_count";
+ field public static final String SLOT_INDEX = "slot_index";
+ field public static final String SUBSCRIPTION_ID = "subscription_id";
+ }
+
+ public static final class SimPhonebookContract.SimRecords {
+ method @NonNull public static android.net.Uri getContentUri(int, int);
+ method @NonNull public static android.net.Uri getItemUri(int, int, int);
+ method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String);
+ field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
+ field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
+ field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+ field public static final String NAME = "name";
+ field public static final String PHONE_NUMBER = "phone_number";
+ field public static final String RECORD_NUMBER = "record_number";
+ field public static final String SUBSCRIPTION_ID = "subscription_id";
+ }
+
+ public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable {
+ ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int);
+ method public int describeContents();
+ method public int getEncodedLength();
+ method public int getMaxEncodedLength();
+ method @NonNull public String getName();
+ method @NonNull public String getSanitizedName();
+ method public boolean isSupportedCharacter(int);
+ method public boolean isValid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR;
+ }
+
public class SyncStateContract {
ctor public SyncStateContract();
}
@@ -36847,6 +36920,7 @@
method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
method @Nullable public java.util.Date getKeyValidityStart();
method @NonNull public String getKeystoreAlias();
+ method public int getMaxUsageCount();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
@@ -36883,6 +36957,7 @@
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -36905,6 +36980,7 @@
method public String getKeystoreAlias();
method public int getOrigin();
method public int getPurposes();
+ method public int getRemainingUsageCount();
method public int getSecurityLevel();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
@@ -36974,6 +37050,7 @@
field public static final int SECURITY_LEVEL_UNKNOWN_SECURE = -1; // 0xffffffff
field public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final String SIGNATURE_PADDING_RSA_PSS = "PSS";
+ field public static final int UNRESTRICTED_USAGE_COUNT = -1; // 0xffffffff
}
public final class KeyProtection implements java.security.KeyStore.ProtectionParameter {
@@ -36983,6 +37060,7 @@
method @Nullable public java.util.Date getKeyValidityForConsumptionEnd();
method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
method @Nullable public java.util.Date getKeyValidityStart();
+ method public int getMaxUsageCount();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
@@ -37009,6 +37087,7 @@
method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForConsumptionEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date);
+ method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int);
method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
@@ -45710,6 +45789,8 @@
method public void clear();
method public android.util.SparseArray<E> clone();
method public boolean contains(int);
+ method public boolean contentEquals(@Nullable android.util.SparseArray<E>);
+ method public int contentHashCode();
method public void delete(int);
method public E get(int);
method public E get(int, E);
@@ -46120,6 +46201,7 @@
method @Deprecated public void getRectSize(android.graphics.Rect);
method public float getRefreshRate();
method public int getRotation();
+ method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
method @Deprecated public void getSize(android.graphics.Point);
method public int getState();
method public android.view.Display.Mode[] getSupportedModes();
@@ -46363,6 +46445,7 @@
public final class InputDevice implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public android.hardware.Battery getBattery();
method public int getControllerNumber();
method public String getDescriptor();
method public static android.view.InputDevice getDevice(int);
@@ -46417,6 +46500,7 @@
field public static final int SOURCE_MOUSE = 8194; // 0x2002
field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
+ field public static final int SOURCE_SENSOR = 67108864; // 0x4000000
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -47372,6 +47456,20 @@
field public static final int TYPE_ZOOM_OUT = 1019; // 0x3fb
}
+ public final class RoundedCorner implements android.os.Parcelable {
+ ctor public RoundedCorner(int, int, int, int);
+ method public int describeContents();
+ method @NonNull public android.graphics.Point getCenter();
+ method public int getPosition();
+ method public int getRadius();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.RoundedCorner> CREATOR;
+ field public static final int POSITION_BOTTOM_LEFT = 3; // 0x3
+ field public static final int POSITION_BOTTOM_RIGHT = 2; // 0x2
+ field public static final int POSITION_TOP_LEFT = 0; // 0x0
+ field public static final int POSITION_TOP_RIGHT = 1; // 0x1
+ }
+
public class ScaleGestureDetector {
ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener);
ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener, android.os.Handler);
@@ -49381,6 +49479,7 @@
method @NonNull public android.graphics.Insets getInsets(int);
method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
+ method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
method @Deprecated public int getStableInsetBottom();
method @Deprecated public int getStableInsetLeft();
method @Deprecated public int getStableInsetRight();
@@ -49414,6 +49513,7 @@
method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setRoundedCorner(int, @Nullable android.view.RoundedCorner);
method @Deprecated @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
@@ -49566,6 +49666,7 @@
field public static final int FLAGS_CHANGED = 4; // 0x4
field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
+ field public static final int FLAG_BLUR_BEHIND = 4; // 0x4
field public static final int FLAG_DIM_BEHIND = 2; // 0x2
field @Deprecated public static final int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
field @Deprecated public static final int FLAG_DITHER = 4096; // 0x1000
@@ -49656,6 +49757,7 @@
field @Deprecated public static final int TYPE_TOAST = 2005; // 0x7d5
field public static final int TYPE_WALLPAPER = 2013; // 0x7dd
field public float alpha;
+ field public int blurBehindRadius;
field public float buttonBrightness;
field public float dimAmount;
field public int flags;
@@ -53058,6 +53160,12 @@
ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int);
ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int, int);
+ method @Deprecated @Nullable public String getTimeZone();
+ method @Deprecated public void setDial(@NonNull android.graphics.drawable.Icon);
+ method @Deprecated public void setHourHand(@NonNull android.graphics.drawable.Icon);
+ method @Deprecated public void setMinuteHand(@NonNull android.graphics.drawable.Icon);
+ method @Deprecated public void setSecondHand(@Nullable android.graphics.drawable.Icon);
+ method @Deprecated public void setTimeZone(@Nullable String);
}
public class ArrayAdapter<T> extends android.widget.BaseAdapter implements android.widget.Filterable android.widget.ThemedSpinnerAdapter {
@@ -54465,19 +54573,26 @@
method public void setByte(@IdRes int, String, byte);
method public void setChar(@IdRes int, String, char);
method public void setCharSequence(@IdRes int, String, CharSequence);
+ method public void setCharSequence(@IdRes int, @NonNull String, @StringRes int);
method public void setChronometer(@IdRes int, long, String, boolean);
method public void setChronometerCountDown(@IdRes int, boolean);
+ method public void setColor(@IdRes int, @NonNull String, @ColorRes int);
+ method public void setColorStateList(@IdRes int, @NonNull String, @ColorRes int);
method public void setContentDescription(@IdRes int, CharSequence);
method public void setDisplayedChild(@IdRes int, int);
method public void setDouble(@IdRes int, String, double);
method public void setEmptyView(@IdRes int, @IdRes int);
method public void setFloat(@IdRes int, String, float);
+ method public void setFloatDimen(@IdRes int, @NonNull String, @DimenRes int);
+ method public void setFloatDimen(@IdRes int, @NonNull String, float, int);
method public void setIcon(@IdRes int, String, android.graphics.drawable.Icon);
method public void setImageViewBitmap(@IdRes int, android.graphics.Bitmap);
method public void setImageViewIcon(@IdRes int, android.graphics.drawable.Icon);
method public void setImageViewResource(@IdRes int, @DrawableRes int);
method public void setImageViewUri(@IdRes int, android.net.Uri);
method public void setInt(@IdRes int, String, int);
+ method public void setIntDimen(@IdRes int, @NonNull String, @DimenRes int);
+ method public void setIntDimen(@IdRes int, @NonNull String, float, int);
method public void setIntent(@IdRes int, String, android.content.Intent);
method public void setLabelFor(@IdRes int, @IdRes int);
method public void setLightBackgroundLayoutId(@LayoutRes int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 0f49dc5..bf70803 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -157,6 +157,10 @@
package android.net {
+ public final class ConnectivityFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ }
+
public class ConnectivityManager {
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);
@@ -178,6 +182,10 @@
field public static final int TRANSPORT_TEST = 7; // 0x7
}
+ public final class Proxy {
+ method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
+ }
+
public final class TcpRepairWindow {
ctor public TcpRepairWindow(int, int, int, int, int, int);
field public final int maxWindow;
@@ -204,6 +212,16 @@
method public void teardownTestNetwork(@NonNull android.net.Network);
}
+ public final class UnderlyingNetworkInfo implements android.os.Parcelable {
+ ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.UnderlyingNetworkInfo> CREATOR;
+ field @NonNull public final String iface;
+ field public final int ownerUid;
+ field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
+ }
+
}
package android.os {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b4cc89e..bea4b39 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -34,6 +34,7 @@
field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
+ field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
@@ -86,6 +87,7 @@
field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
+ field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
@@ -138,6 +140,7 @@
field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER";
field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
+ field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
@@ -257,12 +260,12 @@
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
field public static final String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
+ field public static final String UPDATE_DOMAIN_VERIFICATION_USER_SELECTION = "android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION";
field public static final String UPDATE_FONTS = "android.permission.UPDATE_FONTS";
field public static final String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
field public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
- field public static final String USE_BACKGROUND_BLUR = "android.permission.USE_BACKGROUND_BLUR";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
@@ -294,8 +297,6 @@
field public static final int sdkVersion = 16844304; // 0x1010610
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
- field public static final int windowBackgroundBlurEnabled = 16844316; // 0x101061c
- field public static final int windowBackgroundBlurRadius = 16844315; // 0x101061b
}
public static final class R.bool {
@@ -631,8 +632,6 @@
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(int, long);
method public android.os.Bundle toBundle();
- field public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
- field public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
}
public class DownloadManager {
@@ -1493,6 +1492,169 @@
}
+package android.app.smartspace {
+
+ public final class SmartspaceAction implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public CharSequence getContentDescription();
+ method @Nullable public android.os.Bundle getExtras();
+ method @Nullable public android.graphics.drawable.Icon getIcon();
+ method @NonNull public String getId();
+ method @Nullable public android.content.Intent getIntent();
+ method @Nullable public android.app.PendingIntent getPendingIntent();
+ method @Nullable public CharSequence getSubtitle();
+ method @NonNull public CharSequence getTitle();
+ method @Nullable public android.os.UserHandle getUserHandle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceAction> CREATOR;
+ }
+
+ public static final class SmartspaceAction.Builder {
+ ctor public SmartspaceAction.Builder(@NonNull String, @NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceAction build();
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setContentDescription(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIntent(@Nullable android.content.Intent);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setSubtitle(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+ }
+
+ public final class SmartspaceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public String getPackageName();
+ method @NonNull public int getSmartspaceTargetCount();
+ method @NonNull public String getUiSurface();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceConfig> CREATOR;
+ }
+
+ public static final class SmartspaceConfig.Builder {
+ ctor public SmartspaceConfig.Builder(@NonNull android.content.Context, @NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceConfig build();
+ method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setSmartspaceTargetCount(int);
+ }
+
+ public final class SmartspaceManager {
+ method @NonNull public android.app.smartspace.SmartspaceSession createSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig);
+ }
+
+ public final class SmartspaceSession implements java.lang.AutoCloseable {
+ method public void close();
+ method public void destroy();
+ method protected void finalize();
+ method public void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceTargetEvent);
+ method public void registerSmartspaceUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.smartspace.SmartspaceSession.Callback);
+ method public void requestSmartspaceUpdate();
+ method public void unregisterSmartspaceUpdates(@NonNull android.app.smartspace.SmartspaceSession.Callback);
+ }
+
+ public static interface SmartspaceSession.Callback {
+ method public void onTargetsAvailable(@NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+ }
+
+ public final class SmartspaceSessionId implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getId();
+ method @NonNull public int getUserId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceSessionId> CREATOR;
+ }
+
+ public final class SmartspaceTarget implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getActionChips();
+ method @Nullable public String getAssociatedSmartspaceTargetId();
+ method @Nullable public android.app.smartspace.SmartspaceAction getBaseAction();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @NonNull public long getCreationTimeMillis();
+ method @NonNull public long getExpiryTimeMillis();
+ method @NonNull public int getFeatureType();
+ method @Nullable public android.app.smartspace.SmartspaceAction getHeaderAction();
+ method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getIconGrid();
+ method @NonNull public float getScore();
+ method @Nullable public android.net.Uri getSliceUri();
+ method @NonNull public String getSmartspaceTargetId();
+ method @Nullable public String getSourceNotificationKey();
+ method @NonNull public android.os.UserHandle getUserHandle();
+ method @Nullable public android.appwidget.AppWidgetProviderInfo getWidgetId();
+ method @NonNull public boolean isSensitive();
+ method @NonNull public boolean shouldShowExpanded();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTarget> CREATOR;
+ field public static final int FEATURE_ALARM = 7; // 0x7
+ field public static final int FEATURE_BEDTIME_ROUTINE = 16; // 0x10
+ field public static final int FEATURE_CALENDAR = 2; // 0x2
+ field public static final int FEATURE_COMMUTE_TIME = 3; // 0x3
+ field public static final int FEATURE_CONSENT = 11; // 0xb
+ field public static final int FEATURE_ETA_MONITORING = 18; // 0x12
+ field public static final int FEATURE_FITNESS_TRACKING = 17; // 0x11
+ field public static final int FEATURE_FLIGHT = 4; // 0x4
+ field public static final int FEATURE_LOYALTY_CARD = 14; // 0xe
+ field public static final int FEATURE_MEDIA = 15; // 0xf
+ field public static final int FEATURE_MISSED_CALL = 19; // 0x13
+ field public static final int FEATURE_ONBOARDING = 8; // 0x8
+ field public static final int FEATURE_PACKAGE_TRACKING = 20; // 0x14
+ field public static final int FEATURE_REMINDER = 6; // 0x6
+ field public static final int FEATURE_SHOPPING_LIST = 13; // 0xd
+ field public static final int FEATURE_SPORTS = 9; // 0x9
+ field public static final int FEATURE_STOCK_PRICE_CHANGE = 12; // 0xc
+ field public static final int FEATURE_STOPWATCH = 22; // 0x16
+ field public static final int FEATURE_TIMER = 21; // 0x15
+ field public static final int FEATURE_TIPS = 5; // 0x5
+ field public static final int FEATURE_UNDEFINED = 0; // 0x0
+ field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
+ field public static final int FEATURE_WEATHER = 1; // 0x1
+ field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+ }
+
+ public static final class SmartspaceTarget.Builder {
+ ctor public SmartspaceTarget.Builder(@NonNull String, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+ method @NonNull public android.app.smartspace.SmartspaceTarget build();
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setActionChips(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setAssociatedSmartspaceTargetId(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setBaseAction(@NonNull android.app.smartspace.SmartspaceAction);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setCreationTimeMillis(@NonNull long);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setExpiryTimeMillis(@NonNull long);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setFeatureType(@NonNull int);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setHeaderAction(@NonNull android.app.smartspace.SmartspaceAction);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setIconGrid(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setScore(@NonNull float);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSensitive(@NonNull boolean);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(@NonNull boolean);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidgetId(@NonNull android.appwidget.AppWidgetProviderInfo);
+ }
+
+ public final class SmartspaceTargetEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public int getEventType();
+ method @Nullable public String getSmartspaceActionId();
+ method @Nullable public android.app.smartspace.SmartspaceTarget getSmartspaceTarget();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTargetEvent> CREATOR;
+ field public static final int EVENT_TARGET_BLOCK = 5; // 0x5
+ field public static final int EVENT_TARGET_DISMISS = 4; // 0x4
+ field public static final int EVENT_TARGET_INTERACTION = 1; // 0x1
+ field public static final int EVENT_TARGET_IN_VIEW = 2; // 0x2
+ field public static final int EVENT_TARGET_OUT_OF_VIEW = 3; // 0x3
+ field public static final int EVENT_UI_SURFACE_IN_VIEW = 6; // 0x6
+ field public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7; // 0x7
+ }
+
+ public static final class SmartspaceTargetEvent.Builder {
+ ctor public SmartspaceTargetEvent.Builder(int);
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent build();
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceActionId(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceTarget(@NonNull android.app.smartspace.SmartspaceTarget);
+ }
+
+}
+
package android.app.time {
public final class TimeManager {
@@ -1941,6 +2103,7 @@
field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
+ field public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
field public static final String FONT_SERVICE = "font";
@@ -1955,6 +2118,7 @@
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String SEARCH_UI_SERVICE = "search_ui";
field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
+ field public static final String SMARTSPACE_SERVICE = "smartspace";
field public static final String STATS_MANAGER = "stats";
field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
@@ -1983,12 +2147,13 @@
field public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
field public static final String ACTION_DEVICE_CUSTOMIZATION_READY = "android.intent.action.DEVICE_CUSTOMIZATION_READY";
field public static final String ACTION_DIAL_EMERGENCY = "android.intent.action.DIAL_EMERGENCY";
+ field public static final String ACTION_DOMAINS_NEED_VERIFICATION = "android.intent.action.DOMAINS_NEED_VERIFICATION";
field public static final String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET";
field public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
field public static final String ACTION_INCIDENT_REPORT_READY = "android.intent.action.INCIDENT_REPORT_READY";
field public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
field public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
- field public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+ field @Deprecated public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION";
field public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
@@ -2321,8 +2486,8 @@
method @Nullable public abstract android.content.ComponentName getInstantAppInstallerComponent();
method @Nullable public abstract android.content.ComponentName getInstantAppResolverSettingsComponent();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
- method @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -2346,9 +2511,9 @@
method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
method public void setSystemAppState(@NonNull String, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
- method @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
- method @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -2358,6 +2523,7 @@
field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
+ field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000
@@ -2415,13 +2581,13 @@
field public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; // 0xffffff99
field public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; // 0xffffff9a
field public static final int INSTALL_SUCCEEDED = 1; // 0x1
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
- field public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
- field public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
+ field @Deprecated public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
+ field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
@@ -2549,6 +2715,52 @@
}
+package android.content.pm.verify.domain {
+
+ public final class DomainVerificationInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
+ method @NonNull public java.util.UUID getIdentifier();
+ method @NonNull public String getPackageName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR;
+ }
+
+ public interface DomainVerificationManager {
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames();
+ method public static boolean isStateModifiable(int);
+ method public static boolean isStateVerified(int);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+ field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
+ field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400
+ field public static final int STATE_NO_RESPONSE = 0; // 0x0
+ field public static final int STATE_SUCCESS = 1; // 0x1
+ }
+
+ public final class DomainVerificationRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Set<java.lang.String> getPackageNames();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationRequest> CREATOR;
+ }
+
+ public final class DomainVerificationUserSelection implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Boolean> getHostToUserSelectionMap();
+ method @NonNull public java.util.UUID getIdentifier();
+ method @NonNull public String getPackageName();
+ method @NonNull public android.os.UserHandle getUser();
+ method @NonNull public boolean isLinkHandlingAllowed();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR;
+ }
+
+}
+
package android.content.rollback {
public final class PackageRollbackInfo implements android.os.Parcelable {
@@ -2594,18 +2806,6 @@
}
-package android.graphics.drawable {
-
- public final class BackgroundBlurDrawable extends android.graphics.drawable.Drawable {
- ctor @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public BackgroundBlurDrawable();
- method public void setBlurRadius(int);
- method public void setColor(@ColorInt int);
- method public void setCornerRadius(float);
- method public void setCornerRadius(float, float, float, float);
- }
-
-}
-
package android.graphics.fonts {
public class FontManager {
@@ -2710,6 +2910,9 @@
field public final boolean nightMode;
field public final String packageName;
field public final float powerBrightnessFactor;
+ field public final boolean reduceBrightColors;
+ field public final float reduceBrightColorsOffset;
+ field public final int reduceBrightColorsStrength;
field public final long timeStamp;
}
@@ -4515,10 +4718,6 @@
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
}
- public final class LocationResult implements android.os.Parcelable {
- method @NonNull public static android.location.LocationResult wrap(@NonNull android.location.Location);
- }
-
public final class SatellitePvt implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.location.SatellitePvt.ClockInfo getClockInfo();
@@ -4585,7 +4784,7 @@
method public abstract void onSendExtraCommand(@NonNull String, @Nullable android.os.Bundle);
method public abstract void onSetRequest(@NonNull android.location.provider.ProviderRequest);
method public void reportLocation(@NonNull android.location.Location);
- method public void reportLocation(@NonNull android.location.LocationResult);
+ method public void reportLocations(@NonNull java.util.List<android.location.Location>);
method public void setAllowed(boolean);
method public void setProperties(@NonNull android.location.provider.ProviderProperties);
field public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider";
@@ -4646,6 +4845,7 @@
public static class AudioAttributes.Builder {
method public android.media.AudioAttributes.Builder addBundle(@NonNull android.os.Bundle);
method public android.media.AudioAttributes.Builder setCapturePreset(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioAttributes.Builder setHotwordMode();
method public android.media.AudioAttributes.Builder setInternalCapturePreset(int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
}
@@ -4787,7 +4987,7 @@
}
public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
- ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
+ ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
}
public static class AudioRecord.Builder {
@@ -7116,6 +7316,7 @@
method @Nullable public String getSsid();
method @NonNull public int[] getTransportTypes();
method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+ field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
@@ -8310,6 +8511,8 @@
field public static final int EVENT_MMS = 2; // 0x2
field public static final int EVENT_SMS = 1; // 0x1
field public static final int EVENT_UNSPECIFIED = 0; // 0x0
+ field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
+ field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
}
public class RecoverySystem {
@@ -9160,6 +9363,24 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
}
+ public final class SimPhonebookContract {
+ method @NonNull public static String getEfUriPath(int);
+ field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
+ }
+
+ public static final class SimPhonebookContract.ElementaryFiles {
+ field public static final String EF_ADN_PATH_SEGMENT = "adn";
+ field public static final String EF_FDN_PATH_SEGMENT = "fdn";
+ field public static final String EF_SDN_PATH_SEGMENT = "sdn";
+ field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
+ }
+
+ public static final class SimPhonebookContract.SimRecords {
+ field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT";
+ field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
+ field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+ }
+
public static final class Telephony.Carriers implements android.provider.BaseColumns {
field public static final String APN_SET_ID = "apn_set_id";
field public static final int CARRIER_EDITED = 4; // 0x4
@@ -9452,29 +9673,6 @@
}
-package android.service.attestation {
-
- public abstract class ImpressionAttestationService extends android.app.Service {
- ctor public ImpressionAttestationService();
- method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
- method public abstract boolean onVerifyImpressionToken(@NonNull byte[], @NonNull android.service.attestation.ImpressionToken);
- }
-
- public final class ImpressionToken implements android.os.Parcelable {
- ctor public ImpressionToken(long, @NonNull android.graphics.Rect, @NonNull String, @NonNull byte[], @NonNull byte[]);
- method public int describeContents();
- method @NonNull public android.graphics.Rect getBoundsInWindow();
- method @NonNull public String getHashingAlgorithm();
- method @NonNull public byte[] getHmac();
- method @NonNull public byte[] getImageHash();
- method public long getScreenshotTimeMillis();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.service.attestation.ImpressionToken> CREATOR;
- }
-
-}
-
package android.service.autofill {
public abstract class AutofillFieldClassificationService extends android.app.Service {
@@ -10040,6 +10238,30 @@
}
+package android.service.screenshot {
+
+ public final class ScreenshotHash implements android.os.Parcelable {
+ ctor public ScreenshotHash(long, @NonNull android.graphics.Rect, @NonNull String, @NonNull byte[], @NonNull byte[]);
+ method public int describeContents();
+ method @NonNull public android.graphics.Rect getBoundsInWindow();
+ method @NonNull public String getHashingAlgorithm();
+ method @NonNull public byte[] getHmac();
+ method @NonNull public byte[] getImageHash();
+ method public long getScreenshotTimeMillis();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.screenshot.ScreenshotHash> CREATOR;
+ }
+
+ public abstract class ScreenshotHasherService extends android.app.Service {
+ ctor public ScreenshotHasherService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @Nullable public abstract android.service.screenshot.ScreenshotHash onGenerateScreenshotHash(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
+ method public abstract boolean onVerifyScreenshotHash(@NonNull byte[], @NonNull android.service.screenshot.ScreenshotHash);
+ field public static final String SERVICE_INTERFACE = "android.service.screenshot.ScreenshotHasherService";
+ }
+
+}
+
package android.service.search {
public abstract class SearchUiService extends android.app.Service {
@@ -10088,6 +10310,21 @@
}
+package android.service.smartspace {
+
+ public abstract class SmartspaceService extends android.app.Service {
+ ctor public SmartspaceService();
+ method @MainThread public abstract void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull android.app.smartspace.SmartspaceTargetEvent);
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onCreateSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig, @NonNull android.app.smartspace.SmartspaceSessionId);
+ method @MainThread public abstract void onDestroy(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method public abstract void onDestroySmartspaceSession(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method @MainThread public abstract void onRequestSmartspaceUpdate(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method public final void updateSmartspaceTargets(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+ }
+
+}
+
package android.service.storage {
public abstract class ExternalStorageService extends android.app.Service {
@@ -11639,7 +11876,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
@@ -13172,6 +13409,7 @@
field public static final String IPTYPE_IPV6 = "IPV6";
field public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string";
field public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string";
+ field public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string";
field public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string";
field public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string";
field public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string";
@@ -13204,6 +13442,7 @@
field public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int";
field public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string";
field public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string";
+ field public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string";
field public static final String SIP_TRANSPORT_TCP = "TCP";
field public static final String SIP_TRANSPORT_UDP = "UDP";
}
@@ -13918,10 +14157,8 @@
method public boolean isSystemApplicationOverlay();
method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean);
method public final void setUserActivityTimeout(long);
- field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public static final int FLAG_BLUR_BEHIND = 4; // 0x4
field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10
- field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public int backgroundBlurRadius;
}
@IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
@@ -14191,6 +14428,7 @@
method public String getDataDirectorySuffix();
method public String getErrorString(android.content.Context, int);
method public int getPackageId(android.content.res.Resources, String);
+ method @NonNull public long[] getTimestamps();
method @Deprecated public void invokeDrawGlFunctor(android.view.View, long, boolean);
method public boolean isMultiProcessEnabled();
method public boolean isTraceTagEnabled();
@@ -14206,6 +14444,12 @@
method public static android.content.pm.PackageInfo getLoadedPackageInfo();
method public static int loadWebViewNativeLibraryFromPackage(String, ClassLoader);
method public static void prepareWebViewInZygote();
+ field public static final int ADD_ASSETS_END = 4; // 0x4
+ field public static final int ADD_ASSETS_START = 3; // 0x3
+ field public static final int CREATE_CONTEXT_END = 2; // 0x2
+ field public static final int CREATE_CONTEXT_START = 1; // 0x1
+ field public static final int GET_CLASS_LOADER_END = 6; // 0x6
+ field public static final int GET_CLASS_LOADER_START = 5; // 0x5
field public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2; // 0x2
field public static final int LIBLOAD_FAILED_JNI_CALL = 7; // 0x7
field public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
@@ -14216,6 +14460,11 @@
field public static final int LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN = 8; // 0x8
field public static final int LIBLOAD_SUCCESS = 0; // 0x0
field public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1; // 0x1
+ field public static final int NATIVE_LOAD_END = 8; // 0x8
+ field public static final int NATIVE_LOAD_START = 7; // 0x7
+ field public static final int PROVIDER_CLASS_FOR_NAME_END = 10; // 0xa
+ field public static final int PROVIDER_CLASS_FOR_NAME_START = 9; // 0x9
+ field public static final int WEBVIEW_LOAD_START = 0; // 0x0
}
public interface WebViewFactoryProvider {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 8b3cee4..58f6c52 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -6,9 +6,11 @@
BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
ExecutorRegistration: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
Registration methods should have overload that accepts delivery Executor: `setOnRtpRxNoticeListener`
-
+
+
GenericException: android.app.prediction.AppPredictor#finalize():
GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -21,6 +23,8 @@
IntentBuilderName: android.app.search.SearchAction#getIntent():
+IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent():
+ Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent`
KotlinKeyword: android.app.Notification#when:
@@ -83,6 +87,10 @@
+OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent):
+ Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent`
+
+
ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -187,11 +195,10 @@
SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
- SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-
SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
@@ -258,3 +265,5 @@
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
+UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle):
+ Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bc1858b..eda88e9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -389,7 +389,8 @@
method public boolean isFactoryResetProtectionPolicySupported();
method @NonNull public static String operationToString(int);
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
- method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, boolean);
+ method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
+ method @NonNull public static String unsafeOperationReasonToString(int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_MANAGED_PROFILE_CREATED = "android.app.action.MANAGED_PROFILE_CREATED";
field public static final String ACTION_PROVISIONED_MANAGED_DEVICE = "android.app.action.PROVISIONED_MANAGED_DEVICE";
@@ -401,10 +402,10 @@
field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
- field public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
+ field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
field public static final int CODE_OK = 0; // 0x0
field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
- field public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
+ field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
field public static final int CODE_SYSTEM_USER = 10; // 0xa
field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
@@ -456,6 +457,7 @@
field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
+ field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff
}
public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
@@ -512,6 +514,7 @@
}
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+ ctor public UnsafeStateException(int, int);
method public int getOperation();
}
@@ -2324,6 +2327,8 @@
}
public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
+ method @Nullable public final android.os.IBinder getWindowContextToken();
+ method public final void setWindowContextToken(@NonNull android.os.IBinder);
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
field public CharSequence accessibilityTitle;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bdd541a..4728f11 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5090,6 +5090,13 @@
mTaskDescription.setBackgroundColor(colorBackground);
}
+ int colorBackgroundFloating = a.getColor(
+ com.android.internal.R.styleable.ActivityTaskDescription_colorBackgroundFloating,
+ 0);
+ if (colorBackgroundFloating != 0 && Color.alpha(colorBackgroundFloating) == 0xFF) {
+ mTaskDescription.setBackgroundColorFloating(colorBackgroundFloating);
+ }
+
final int statusBarColor = a.getColor(
com.android.internal.R.styleable.ActivityTaskDescription_statusBarColor, 0);
if (statusBarColor != 0) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 520959c..43d0269 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1072,13 +1072,15 @@
private static final String ATTR_TASKDESCRIPTIONCOLOR_PRIMARY =
ATTR_TASKDESCRIPTION_PREFIX + "color";
private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
- ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
+ ATTR_TASKDESCRIPTION_PREFIX + "color_background";
private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE_PACKAGE =
ATTR_TASKDESCRIPTION_PREFIX + "icon_package";
+ private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING =
+ ATTR_TASKDESCRIPTION_PREFIX + "color_background_floating";
private String mLabel;
@Nullable
@@ -1086,6 +1088,7 @@
private String mIconFilename;
private int mColorPrimary;
private int mColorBackground;
+ private int mColorBackgroundFloating;
private int mStatusBarColor;
private int mNavigationBarColor;
private boolean mEnsureStatusBarContrastWhenTransparent;
@@ -1106,7 +1109,7 @@
*/
public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+ colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1121,7 +1124,7 @@
*/
public TaskDescription(String label, @DrawableRes int iconRes) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+ 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1130,14 +1133,14 @@
* @param label A label and description of the current state of this activity.
*/
public TaskDescription(String label) {
- this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+ this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
* Creates an empty TaskDescription.
*/
public TaskDescription() {
- this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+ this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1152,7 +1155,7 @@
@Deprecated
public TaskDescription(String label, Bitmap icon, int colorPrimary) {
this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
- false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+ false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1168,7 +1171,7 @@
@Deprecated
public TaskDescription(String label, Bitmap icon) {
this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
- RESIZE_MODE_RESIZEABLE, -1, -1);
+ RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/** @hide */
@@ -1177,7 +1180,7 @@
int statusBarColor, int navigationBarColor,
boolean ensureStatusBarContrastWhenTransparent,
boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
- int minHeight) {
+ int minHeight, int colorBackgroundFloating) {
mLabel = label;
mIcon = icon;
mColorPrimary = colorPrimary;
@@ -1190,6 +1193,7 @@
mResizeMode = resizeMode;
mMinWidth = minWidth;
mMinHeight = minHeight;
+ mColorBackgroundFloating = colorBackgroundFloating;
}
/**
@@ -1217,6 +1221,7 @@
mResizeMode = other.mResizeMode;
mMinWidth = other.mMinWidth;
mMinHeight = other.mMinHeight;
+ mColorBackgroundFloating = other.mColorBackgroundFloating;
}
/**
@@ -1253,6 +1258,9 @@
if (other.mMinHeight != -1) {
mMinHeight = other.mMinHeight;
}
+ if (other.mColorBackgroundFloating != 0) {
+ mColorBackgroundFloating = other.mColorBackgroundFloating;
+ }
}
private TaskDescription(Parcel source) {
@@ -1292,6 +1300,19 @@
}
/**
+ * Sets the background color floating for this task description.
+ * @hide
+ */
+ public void setBackgroundColorFloating(int backgroundColor) {
+ // Ensure that the given color is valid
+ if ((backgroundColor != 0) && (Color.alpha(backgroundColor) != 255)) {
+ throw new RuntimeException(
+ "A TaskDescription's background color floating should be opaque");
+ }
+ mColorBackgroundFloating = backgroundColor;
+ }
+
+ /**
* @hide
*/
public void setStatusBarColor(int statusBarColor) {
@@ -1461,6 +1482,14 @@
}
/**
+ * @return The background color floating.
+ * @hide
+ */
+ public int getBackgroundColorFloating() {
+ return mColorBackgroundFloating;
+ }
+
+ /**
* @hide
*/
public int getStatusBarColor() {
@@ -1537,6 +1566,10 @@
if (mColorBackground != 0) {
out.attributeIntHex(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND, mColorBackground);
}
+ if (mColorBackgroundFloating != 0) {
+ out.attributeIntHex(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING,
+ mColorBackgroundFloating);
+ }
if (mIconFilename != null) {
out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
}
@@ -1563,6 +1596,11 @@
if (colorBackground != 0) {
setBackgroundColor(colorBackground);
}
+ final int colorBackgroundFloating = in.getAttributeIntHex(null,
+ ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING, 0);
+ if (colorBackgroundFloating != 0) {
+ setBackgroundColorFloating(colorBackgroundFloating);
+ }
final String iconFilename = in.getAttributeValue(null,
ATTR_TASKDESCRIPTIONICON_FILENAME);
if (iconFilename != null) {
@@ -1615,6 +1653,7 @@
dest.writeInt(1);
dest.writeString(mIconFilename);
}
+ dest.writeInt(mColorBackgroundFloating);
}
public void readFromParcel(Parcel source) {
@@ -1632,6 +1671,7 @@
mMinWidth = source.readInt();
mMinHeight = source.readInt();
mIconFilename = source.readInt() > 0 ? source.readString() : null;
+ mColorBackgroundFloating = source.readInt();
}
public static final @android.annotation.NonNull Creator<TaskDescription> CREATOR
@@ -1655,7 +1695,8 @@
+ (mEnsureNavigationBarContrastWhenTransparent
? " (contrast when transparent)" : "")
+ " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
- + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight;
+ + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
+ + " colorBackgrounFloating: " + mColorBackgroundFloating;
}
@Override
@@ -1678,7 +1719,8 @@
== other.mEnsureNavigationBarContrastWhenTransparent
&& mResizeMode == other.mResizeMode
&& mMinWidth == other.mMinWidth
- && mMinHeight == other.mMinHeight;
+ && mMinHeight == other.mMinHeight
+ && mColorBackgroundFloating == other.mColorBackgroundFloating;
}
/** @hide */
@@ -1826,6 +1868,8 @@
pw.print(ActivityInfo.resizeModeToString(td.getResizeMode()));
pw.print(" minWidth="); pw.print(td.getMinWidth());
pw.print(" minHeight="); pw.print(td.getMinHeight());
+ pw.print(" colorBackgroundFloating=#");
+ pw.print(Integer.toHexString(td.getBackgroundColorFloating()));
pw.println(" }");
}
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7402690..9b6f4b4 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -30,6 +30,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.PowerWhitelistManager.TempAllowListType;
import android.os.TransactionTooLargeException;
import android.os.WorkSource;
import android.util.ArraySet;
@@ -103,7 +104,7 @@
* @param target
* @param whitelistToken
* @param duration temp allowlist duration in milliseconds.
- * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+ * @param type temp allowlist type defined at {@link TempAllowListType}
*/
public abstract void setPendingIntentWhitelistDuration(IIntentSender target,
IBinder whitelistToken, long duration, int type);
@@ -136,10 +137,10 @@
* @param changingUid uid to add or remove to temp allowlist.
* @param adding true to add to temp allowlist, false to remove from temp allowlist.
* @param durationMs when adding is true, the duration to be in temp allowlist.
- * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}.
+ * @param type temp allowlist type defined at {@link TempAllowListType}.
*/
public abstract void updateDeviceIdleTempWhitelist(int[] appids, int changingUid,
- boolean adding, long durationMs, @BroadcastOptions.TempAllowListType int type);
+ boolean adding, long durationMs, @TempAllowListType int type);
/**
* Get the procstate for the UID. The return value will be between
@@ -333,7 +334,7 @@
* @param callerUid the UID that sent the PendingIntent.
* @param targetUid the UID that is been temp allowlisted.
* @param duration temp allowlist duration in milliseconds.
- * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+ * @param type temp allowlist type defined at {@link TempAllowListType}
* @param tag
*/
public abstract void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid,
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1f9cb64..e5a04c9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -95,7 +95,6 @@
import android.media.MediaFrameworkPlatformInitializer;
import android.media.MediaServiceManager;
import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.Uri;
import android.os.AsyncTask;
@@ -6576,25 +6575,6 @@
// Pass the current context to HardwareRenderer
HardwareRenderer.setContextForInit(getSystemContext());
- /**
- * Initialize the default http proxy in this process for the reasons we set the time zone.
- */
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
- final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- if (b != null) {
- // In pre-boot mode (doing initial launch to collect password), not
- // all system is up. This includes the connectivity service, so don't
- // crash if we can't get it.
- final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- try {
- Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null));
- } catch (RemoteException e) {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- throw e.rethrowFromSystemServer();
- }
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
// Instrumentation info affects the class loader, so load it before
// setting up the app context.
final InstrumentationInfo ii;
@@ -6608,6 +6588,23 @@
updateLocaleListFromAppContext(appContext,
mResourcesManager.getConfiguration().getLocales());
+ // Initialize the default http proxy in this process.
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
+ try {
+ // In pre-boot mode (doing initial launch to collect password), not all system is up.
+ // This includes the connectivity service, so trying to obtain ConnectivityManager at
+ // that point would return null. Check whether the ConnectivityService is available, and
+ // avoid crashing with a NullPointerException if it is not.
+ final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ if (b != null) {
+ final ConnectivityManager cm =
+ appContext.getSystemService(ConnectivityManager.class);
+ Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
if (!Process.isIsolated()) {
final int oldMask = StrictMode.allowThreadDiskWritesMask();
try {
@@ -7522,8 +7519,8 @@
}
public static void updateHttpProxy(@NonNull Context context) {
- final ConnectivityManager cm = ConnectivityManager.from(context);
- Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
+ final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+ Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d716a3c..7410a1c 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -50,7 +50,7 @@
*
* <p>
* Application process could die for many reasons, for example {@link #REASON_LOW_MEMORY}
- * when it was killed by the ystem because it was running low on memory. Reason
+ * when it was killed by the system because it was running low on memory. Reason
* of the death can be retrieved via {@link #getReason}. Besides the reason, there are a few other
* auxiliary APIs like {@link #getStatus} and {@link #getImportance} to help the caller with
* additional diagnostic information.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7e7f887..b51d4ac 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -59,6 +59,7 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageUserState;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
@@ -70,6 +71,12 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.PackageInfoWithoutStateUtils;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -116,6 +123,7 @@
import libcore.util.EmptyArray;
+import java.io.File;
import java.lang.ref.WeakReference;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
@@ -2055,6 +2063,31 @@
return info.loadLabel(this);
}
+ @Nullable
+ public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
+ @PackageInfoFlags int flags) {
+ if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
+ // Caller expressed no opinion about what encryption
+ // aware/unaware components they want to see, so match both
+ flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ }
+
+ boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
+ || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
+
+ ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
+ ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
+ new File(archiveFilePath), 0, getPermissionManager().getSplitPermissions(),
+ collectCertificates);
+ if (result.isError()) {
+ return null;
+ }
+ return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
+ new PackageUserState(), UserHandle.getCallingUserId());
+ }
+
@Override
public int installExistingPackage(String packageName) throws NameNotFoundException {
return installExistingPackage(packageName, INSTALL_REASON_UNKNOWN);
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index a16f6a8..445fdd8 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -16,14 +16,12 @@
package android.app;
-import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Build;
import android.os.Bundle;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import android.os.PowerWhitelistManager;
+import android.os.PowerWhitelistManager.TempAllowListType;
/**
* Helper class for building an options Bundle that can be used with
@@ -75,25 +73,21 @@
"android:broadcast.allowBackgroundActivityStarts";
/**
- * Allow the temp allowlist behavior, plus allow foreground service start from background.
- */
- public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0;
- /**
- * Only allow the temp allowlist behavior, not allow foreground service start from
- * background.
- */
- public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1;
-
- /**
- * The list of temp allowlist types.
* @hide
+ * @deprecated Use {@link android.os.PowerWhitelistManager#
+ * TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
*/
- @IntDef(flag = true, prefix = { "TEMPORARY_WHITELIST_TYPE_" }, value = {
- TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
- TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TempAllowListType {}
+ @Deprecated
+ public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED =
+ PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+ /**
+ * @hide
+ * @deprecated Use {@link android.os.PowerWhitelistManager#
+ * TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} instead.
+ */
+ @Deprecated
+ public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED =
+ PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
@@ -125,7 +119,8 @@
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
public void setTemporaryAppWhitelistDuration(long duration) {
mTemporaryAppWhitelistDuration = duration;
- mTemporaryAppWhitelistType = TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+ mTemporaryAppWhitelistType =
+ PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
}
/**
diff --git a/graphics/java/android/graphics/GameManager.java b/core/java/android/app/GameManager.java
similarity index 98%
rename from graphics/java/android/graphics/GameManager.java
rename to core/java/android/app/GameManager.java
index a58aeb4..8b6570f 100644
--- a/graphics/java/android/graphics/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.graphics;
+package android.app;
import android.annotation.IntDef;
import android.annotation.SystemService;
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index c2c62c1..9d3286f 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -96,7 +96,14 @@
oneway void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked);
oneway void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
oneway void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
- oneway void overridePendingTransition(in IBinder token, in String packageName,
+ /**
+ * Overrides the animation of activity pending transition. This call is not one-way because
+ * the method is usually used after startActivity or Activity#finish. If this is non-blocking,
+ * the calling activity may proceed to complete pause and become stopping state, which will
+ * cause the request to be ignored. Besides, startActivity and Activity#finish are blocking
+ * calls, so this method should be the same as them to keep the invocation order.
+ */
+ void overridePendingTransition(in IBinder token, in String packageName,
int enterAnim, int exitAnim);
int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
diff --git a/graphics/java/android/graphics/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
similarity index 96%
rename from graphics/java/android/graphics/IGameManagerService.aidl
rename to core/java/android/app/IGameManagerService.aidl
index 7d3a4fb..c8e1478 100644
--- a/graphics/java/android/graphics/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -14,7 +14,7 @@
** limitations under the License.
*/
-package android.graphics;
+package android.app;
/**
* @hide
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 9e1c505..a9e28bb 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -145,18 +145,6 @@
void onTaskSnapshotChanged(int taskId, in TaskSnapshot snapshot);
/**
- * Called when the resumed activity is in size compatibility mode and its override configuration
- * is different from the current one of system.
- *
- * @param displayId Id of the display where the activity resides.
- * @param activityToken Token of the size compatibility mode activity. It will be null when
- * switching to a activity that is not in size compatibility mode or the
- * configuration of the activity.
- * @see com.android.server.wm.ActivityRecord#inSizeCompatMode
- */
- void onSizeCompatModeActivityChanged(int displayId, in IBinder activityToken);
-
- /**
* Reports that an Activity received a back key press when there were no additional activities
* on the back stack.
*
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 534f3e2..671315f 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemApi.Client;
import android.annotation.TestApi;
@@ -1275,6 +1276,7 @@
* @param flags MATCH_* flags from {@link android.content.pm.PackageManager}.
* @hide
*/
+ @SuppressLint("NullableCollection")
@RequiresPermission(permission.GET_INTENT_SENDER_INTENT)
@SystemApi(client = Client.MODULE_LIBRARIES)
@TestApi
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 1498dae7..7404e53 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,7 @@
import android.app.role.RoleFrameworkInitializer;
import android.app.search.SearchUiManager;
import android.app.slice.SliceManager;
+import android.app.smartspace.SmartspaceManager;
import android.app.time.TimeManager;
import android.app.timedetector.TimeDetector;
import android.app.timedetector.TimeDetectorImpl;
@@ -68,11 +69,13 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationManagerImpl;
+import android.content.pm.verify.domain.IDomainVerificationManager;
import android.content.res.Resources;
import android.content.rollback.RollbackManagerFrameworkInitializer;
import android.debug.AdbManager;
import android.debug.IAdbManager;
-import android.graphics.GameManager;
import android.graphics.fonts.FontManager;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -120,21 +123,16 @@
import android.media.tv.TvInputManager;
import android.media.tv.tunerresourcemanager.ITunerResourceManager;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
-import android.net.ConnectivityDiagnosticsManager;
-import android.net.ConnectivityManager;
+import android.net.ConnectivityFrameworkInitializer;
import android.net.EthernetManager;
-import android.net.IConnectivityManager;
import android.net.IEthernetManager;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
-import android.net.ITestNetworkManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
-import android.net.TestNetworkManager;
import android.net.TetheringManager;
-import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -163,7 +161,6 @@
import android.os.IncidentManager;
import android.os.PowerManager;
import android.os.RecoverySystem;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StatsFrameworkInitializer;
@@ -370,15 +367,6 @@
// (which extends it).
SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
- registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
- new StaticApplicationContextServiceFetcher<ConnectivityManager>() {
- @Override
- public ConnectivityManager createService(Context context) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- return new ConnectivityManager(context, service);
- }});
-
registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
@Override
public IBinder createService() throws ServiceNotFoundException {
@@ -412,50 +400,6 @@
return new IpSecManager(ctx, service);
}});
- registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
- new CachedServiceFetcher<VpnManager>() {
- @Override
- public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- return new VpnManager(ctx, service);
- }});
-
- registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
- ConnectivityDiagnosticsManager.class,
- new CachedServiceFetcher<ConnectivityDiagnosticsManager>() {
- @Override
- public ConnectivityDiagnosticsManager createService(ContextImpl ctx)
- throws ServiceNotFoundException {
- // ConnectivityDiagnosticsManager is backed by ConnectivityService
- IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- return new ConnectivityDiagnosticsManager(ctx, service);
- }});
-
- registerService(
- Context.TEST_NETWORK_SERVICE,
- TestNetworkManager.class,
- new StaticApplicationContextServiceFetcher<TestNetworkManager>() {
- @Override
- public TestNetworkManager createService(Context context)
- throws ServiceNotFoundException {
- IBinder csBinder =
- ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager csMgr =
- IConnectivityManager.Stub.asInterface(csBinder);
-
- final IBinder tnBinder;
- try {
- tnBinder = csMgr.startOrGetTestNetworkService();
- } catch (RemoteException e) {
- throw new ServiceNotFoundException(Context.TEST_NETWORK_SERVICE);
- }
- ITestNetworkManager tnMgr = ITestNetworkManager.Stub.asInterface(tnBinder);
- return new TestNetworkManager(tnMgr);
- }
- });
-
registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
new StaticServiceFetcher<CountryDetector>() {
@Override
@@ -1224,6 +1168,16 @@
}
});
+ registerService(Context.SMARTSPACE_SERVICE, SmartspaceManager.class,
+ new CachedServiceFetcher<SmartspaceManager>() {
+ @Override
+ public SmartspaceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+ return b == null ? null : new SmartspaceManager(ctx);
+ }
+ });
+
registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
new CachedServiceFetcher<AppPredictionManager>() {
@Override
@@ -1437,10 +1391,25 @@
}
});
+ // TODO(b/159952358): Only register this service for the domain verification agent?
+ registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class,
+ new CachedServiceFetcher<DomainVerificationManager>() {
+ @Override
+ public DomainVerificationManager createService(ContextImpl context)
+ throws ServiceNotFoundException {
+ IBinder binder = ServiceManager.getServiceOrThrow(
+ Context.DOMAIN_VERIFICATION_SERVICE);
+ IDomainVerificationManager service =
+ IDomainVerificationManager.Stub.asInterface(binder);
+ return new DomainVerificationManagerImpl(context, service);
+ }
+ });
+
sInitializing = true;
try {
// Note: the following functions need to be @SystemApis, once they become mainline
// modules.
+ ConnectivityFrameworkInitializer.registerServiceWrappers();
JobSchedulerFrameworkInitializer.registerServiceWrappers();
BlobStoreManagerFrameworkInitializer.initialize();
TelephonyFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index e31e024..390d921 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -337,6 +337,23 @@
}
/**
+ * @return {@code true} if parameters that are important for size compat have changed.
+ * @hide
+ */
+ public boolean equalsForSizeCompat(@Nullable TaskInfo that) {
+ if (that == null) {
+ return false;
+ }
+ return displayId == that.displayId
+ && taskId == that.taskId
+ && topActivityInSizeCompat == that.topActivityInSizeCompat
+ // TopActivityToken and bounds are important if top activity is in size compat
+ && (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken))
+ && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
+ .equals(that.configuration.windowConfiguration.getBounds()));
+ }
+
+ /**
* Reads the TaskInfo from a parcel.
*/
void readFromParcel(Parcel source) {
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index d1b544d..517ae24 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -21,7 +21,6 @@
import android.content.ComponentName;
import android.os.Binder;
import android.os.Build;
-import android.os.IBinder;
import android.os.RemoteException;
import android.window.TaskSnapshot;
@@ -163,12 +162,6 @@
}
@Override
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken)
- throws RemoteException {
- }
-
- @Override
public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)
throws RemoteException {
}
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 15ff531..8b0c706 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -51,6 +51,12 @@
public abstract int getPasswordQuality(@UserIdInt int userHandle);
/**
+ * Caches {@link DevicePolicyManager#getPermissionPolicy(android.content.ComponentName)} of
+ * the given user.
+ */
+ public abstract int getPermissionPolicy(@UserIdInt int userHandle);
+
+ /**
* Empty implementation.
*/
private static class EmptyDevicePolicyCache extends DevicePolicyCache {
@@ -66,5 +72,10 @@
public int getPasswordQuality(int userHandle) {
return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
}
+
+ @Override
+ public int getPermissionPolicy(int userHandle) {
+ return DevicePolicyManager.PERMISSION_POLICY_PROMPT;
+ }
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ada703b..642bce4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -453,59 +453,6 @@
"android.app.action.PROVISION_FINANCED_DEVICE";
/**
- * Activity action: Starts the provisioning flow which sets up a managed device.
- * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
- *
- * <p>NOTE: This is only supported on split system user devices, and puts the device into a
- * management state that is distinct from that reached by
- * {@link #ACTION_PROVISION_MANAGED_DEVICE} - specifically the device owner runs on the system
- * user, and only has control over device-wide policies, not individual users and their data.
- * The primary benefit is that multiple non-system users are supported when provisioning using
- * this form of device management.
- *
- * <p>During device owner provisioning a device admin app is set as the owner of the device.
- * A device owner has full control over the device. The device owner can not be modified by the
- * user.
- *
- * <p>A typical use case would be a device that is owned by a company, but used by either an
- * employee or client.
- *
- * <p>An intent with this action can be sent only on an unprovisioned device.
- * It is possible to check if provisioning is allowed or not by querying the method
- * {@link #isProvisioningAllowed(String)}.
- *
- * <p>The intent contains the following extras:
- * <ul>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
- * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
- * </ul>
- *
- * <p>When device owner provisioning has completed, an intent of the type
- * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
- * device owner.
- *
- * <p>From version {@link android.os.Build.VERSION_CODES#O}, when device owner provisioning has
- * completed, along with the above broadcast, activity intent
- * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the device owner.
- *
- * <p>If provisioning fails, the device is factory reset.
- *
- * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
- * of the provisioning flow was successful, although this doesn't guarantee the full flow will
- * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
- * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
- = "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
-
- /**
* Activity action: Finalizes management provisioning, should be used after user-setup
* has been completed and {@link #getUserProvisioningState()} returns one of:
* <ul>
@@ -1990,8 +1937,8 @@
* Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when provisioning is allowed.
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
+ * when provisioning is allowed.
*
* @hide
*/
@@ -2001,9 +1948,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the device already has a device
- * owner.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the device already has a
+ * device owner.
*
* @hide
*/
@@ -2013,9 +1959,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user has a profile owner and for
- * {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user has a profile owner
+ * and for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
*
* @hide
*/
@@ -2025,8 +1970,7 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user isn't running.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user isn't running.
*
* @hide
*/
@@ -2036,9 +1980,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the device has already been setup and
- * for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the device has already been
+ * setup and for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
*
* @hide
*/
@@ -2064,8 +2007,7 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the user is not a system user.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the user is not a system user.
*
* @hide
*/
@@ -2075,9 +2017,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} and {@link #ACTION_PROVISION_MANAGED_USER}
- * when the device is a watch and is already paired.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
+ * {@link #ACTION_PROVISION_MANAGED_USER} when the device is a watch and is already paired.
*
* @hide
*/
@@ -2121,14 +2062,11 @@
/**
* TODO (b/137101239): clean up split system user codes
- * Result code for {@link #checkProvisioningPreCondition}.
- *
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices not running with split system
- * user.
*
* @hide
- */
+ * @deprecated not used anymore but can't be removed since it's a @TestApi.
+ **/
+ @Deprecated
@TestApi
public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
@@ -2136,8 +2074,7 @@
* Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices which do not support device
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support device
* admins.
*
* @hide
@@ -2149,11 +2086,10 @@
* TODO (b/137101239): clean up split system user codes
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a
- * system user on a split system user device.
- *
* @hide
+ * @deprecated not used anymore but can't be removed since it's a @TestApi.
*/
+ @Deprecated
@TestApi
public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
@@ -2969,6 +2905,35 @@
return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
}
+ private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_";
+
+ /** @hide */
+ @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = {
+ UNSAFE_OPERATION_REASON_NONE,
+ UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public static @interface UnsafeOperationReason {
+ }
+
+ /** @hide */
+ @TestApi
+ public static final int UNSAFE_OPERATION_REASON_NONE = -1;
+
+ /**
+ * Indicates that a {@link UnsafeStateException} was thrown because the operation would distract
+ * the driver of the vehicle.
+ */
+ public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1;
+
+ /** @hide */
+ @NonNull
+ @TestApi
+ public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) {
+ return DebugUtils.constantToString(DevicePolicyManager.class,
+ PREFIX_UNSAFE_OPERATION_REASON, reason);
+ }
+
/** @hide */
public void resetNewUserDisclaimer() {
if (mService != null) {
@@ -7147,17 +7112,9 @@
}
/**
+ * TODO (b/137101239): remove this method in follow-up CL
+ * since it's only used for split system user.
* Called by a device owner to set whether all users created on the device should be ephemeral.
- * <p>
- * The system user is exempt from this policy - it is never ephemeral.
- * <p>
- * The calling device admin must be the device owner. If it is not, a security exception will be
- * thrown.
- *
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param forceEphemeralUsers If true, all the existing users will be deleted and all
- * subsequently created users will be ephemeral.
- * @throws SecurityException if {@code admin} is not a device owner.
* @hide
*/
public void setForceEphemeralUsers(
@@ -7173,6 +7130,8 @@
}
/**
+ * TODO (b/137101239): remove this method in follow-up CL
+ * since it's only used for split system user.
* @return true if all users are created ephemeral.
* @throws SecurityException if {@code admin} is not a device owner.
* @hide
@@ -10731,9 +10690,7 @@
* profile or user, setting the given package as owner.
*
* @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_PROFILE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_USER}
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE}
* @param packageName The package of the component that would be set as device, user, or profile
* owner.
* @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
@@ -13170,10 +13127,11 @@
*/
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS)
- public void setNextOperationSafety(@DevicePolicyOperation int operation, boolean safe) {
+ public void setNextOperationSafety(@DevicePolicyOperation int operation,
+ @UnsafeOperationReason int reason) {
if (mService != null) {
try {
- mService.setNextOperationSafety(operation, safe);
+ mService.setNextOperationSafety(operation, reason);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -13270,7 +13228,8 @@
return null;
}
try {
- return mService.createAndProvisionManagedProfile(provisioningParams);
+ return mService.createAndProvisionManagedProfile(
+ provisioningParams, mContext.getPackageName());
} catch (ServiceSpecificException e) {
throw new ProvisioningException(e, e.errorCode);
} catch (RemoteException e) {
@@ -13301,7 +13260,7 @@
throws ProvisioningException {
if (mService != null) {
try {
- mService.provisionFullyManagedDevice(provisioningParams);
+ mService.provisionFullyManagedDevice(provisioningParams, mContext.getPackageName());
} catch (ServiceSpecificException e) {
throw new ProvisioningException(e, e.errorCode);
} catch (RemoteException re) {
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
index b1a80c5..6c6f2aa 100644
--- a/core/java/android/app/admin/DevicePolicySafetyChecker.java
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
import com.android.internal.os.IResultReceiver;
@@ -30,14 +31,16 @@
/**
* Returns whether the given {@code operation} can be safely executed at the moment.
*/
- boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation);
+ @UnsafeOperationReason
+ int getUnsafeOperationReason(@DevicePolicyOperation int operation);
/**
* Returns a new exception for when the given {@code operation} cannot be safely executed.
*/
@NonNull
- default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation) {
- return new UnsafeStateException(operation);
+ default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation,
+ @UnsafeOperationReason int reason) {
+ return new UnsafeStateException(operation, reason);
}
/**
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3765a67..f9ee153 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -491,11 +491,11 @@
void setManagedProfileMaximumTimeOff(in ComponentName admin, long timeoutMs);
boolean canProfileOwnerResetPasswordWhenLocked(int userId);
- void setNextOperationSafety(int operation, boolean safe);
+ void setNextOperationSafety(int operation, int reason);
String getEnrollmentSpecificId(String callerPackage);
void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
- UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams);
- void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams);
+ UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+ void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage);
}
diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
index 9dcaae4..56eeb06 100644
--- a/core/java/android/app/admin/UnsafeStateException.java
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -15,15 +15,21 @@
*/
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION;
+import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
+
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
/**
- * Exception thrown when a {@link DevicePolicyManager} operation failed because it was not safe
- * to be executed at that moment.
+ * Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it
+ * was not safe to be executed at that moment.
*
* <p>For example, it can be thrown on
* {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive devices} when the vehicle
@@ -33,12 +39,19 @@
public final class UnsafeStateException extends IllegalStateException implements Parcelable {
private final @DevicePolicyOperation int mOperation;
+ private final @UnsafeOperationReason int mReason;
/** @hide */
- public UnsafeStateException(@DevicePolicyOperation int operation) {
+ @TestApi
+ public UnsafeStateException(@DevicePolicyOperation int operation,
+ @UnsafeOperationReason int reason) {
super();
-
+ Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
+ "invalid reason %d, must be %d (%s)", reason,
+ UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
+ unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION));
mOperation = operation;
+ mReason = reason;
}
/** @hide */
@@ -47,6 +60,22 @@
return mOperation;
}
+ /**
+ * Gets the reason the operation is unsafe.
+ *
+ * @return currently, only valid reason is
+ * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}.
+ */
+ public @UnsafeOperationReason int getReason() {
+ return mReason;
+ }
+
+ /** @hide */
+ @Override
+ public String getMessage() {
+ return DevicePolicyManager.unsafeOperationReasonToString(mReason);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -55,6 +84,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mOperation);
+ dest.writeInt(mReason);
}
@NonNull
@@ -63,7 +93,7 @@
@Override
public UnsafeStateException createFromParcel(Parcel source) {
- return new UnsafeStateException(source.readInt());
+ return new UnsafeStateException(source.readInt(), source.readInt());
}
@Override
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 65a2164..2b52875 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2,6 +2,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Activity;
import android.content.ComponentName;
@@ -1542,6 +1543,7 @@
* {@link View#getOnReceiveContentMimeTypes()} for details.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public String[] getOnReceiveContentMimeTypes() {
return mOnReceiveContentMimeTypes;
}
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index 447ca31..3ab20bb 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
@@ -46,6 +47,7 @@
public Query(@NonNull String input,
long timestamp,
+ @SuppressLint("NullableCollection")
@Nullable Bundle extras) {
mInput = input;
mTimestamp = timestamp;
@@ -69,6 +71,7 @@
}
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
diff --git a/core/java/android/app/search/SearchAction.java b/core/java/android/app/search/SearchAction.java
index a76154a..9e40e7e 100644
--- a/core/java/android/app/search/SearchAction.java
+++ b/core/java/android/app/search/SearchAction.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Intent;
@@ -167,6 +168,7 @@
/**
* Returns the extra bundle for this object.
*/
+ @SuppressLint("NullableCollection")
public @Nullable Bundle getExtras() {
return mExtras;
}
@@ -325,7 +327,8 @@
* Sets the extra.
*/
@NonNull
- public SearchAction.Builder setExtras(@Nullable Bundle extras) {
+ public SearchAction.Builder setExtras(
+ @SuppressLint("NullableCollection") @Nullable Bundle extras) {
mExtras = extras;
return this;
}
diff --git a/core/java/android/app/search/SearchContext.java b/core/java/android/app/search/SearchContext.java
index 9bf766d..548b7da 100644
--- a/core/java/android/app/search/SearchContext.java
+++ b/core/java/android/app/search/SearchContext.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
@@ -52,7 +53,7 @@
public SearchContext(int resultTypes,
int queryTimeoutMillis,
- @Nullable Bundle extras) {
+ @SuppressLint("NullableCollection") @Nullable Bundle extras) {
mResultTypes = resultTypes;
mTimeoutMillis = queryTimeoutMillis;
mExtras = extras;
@@ -83,6 +84,7 @@
}
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java
index cac22d81..6a80f8b 100644
--- a/core/java/android/app/search/SearchTarget.java
+++ b/core/java/android/app/search/SearchTarget.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.appwidget.AppWidgetProviderInfo;
import android.content.pm.ShortcutInfo;
@@ -224,6 +225,7 @@
* Return extra bundle.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
@@ -386,7 +388,7 @@
* TODO: add comment
*/
@NonNull
- public Builder setExtras(@Nullable Bundle extras) {
+ public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
mExtras = extras;
return this;
}
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
similarity index 76%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to core/java/android/app/smartspace/ISmartspaceCallback.aidl
index 19b20f2..df105f9 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
@@ -14,7 +14,14 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+package android.app.smartspace;
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface ISmartspaceCallback {
+
+ void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/smartspace/ISmartspaceManager.aidl b/core/java/android/app/smartspace/ISmartspaceManager.aidl
new file mode 100644
index 0000000..e7ec889
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceManager.aidl
@@ -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.
+ */
+
+package android.app.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface ISmartspaceManager {
+
+ void createSmartspaceSession(in SmartspaceConfig config, in SmartspaceSessionId sessionId,
+ in IBinder token);
+
+ void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+ void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+ void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void destroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/app/smartspace/OWNERS b/core/java/android/app/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/app/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
new file mode 100644
index 0000000..439d851
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceAction} represents an action which can be taken by a user by tapping on either
+ * the title, the subtitle or on the icon. Supported instances are Intents, PendingIntents or a
+ * ShortcutInfo (by putting the ShortcutInfoId in the bundle). These actions can be called from
+ * another process or within the client process.
+ *
+ * Clients can also receive conditional Intents/PendingIntents in the extras bundle which are
+ * supposed to be fired when the conditions are met. For example, a user can invoke a dismiss/block
+ * action on a game score card but the intention is to only block the team and not the entire
+ * feature.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceAction implements Parcelable {
+
+ private static final String TAG = "SmartspaceAction";
+
+ /** A unique Id of this {@link SmartspaceAction}. */
+ @NonNull
+ private final String mId;
+
+ /** An Icon which can be displayed in the UI. */
+ @Nullable
+ private final Icon mIcon;
+
+ /** Title associated with an action. */
+ @NonNull
+ private final CharSequence mTitle;
+
+ /** Subtitle associated with an action. */
+ @Nullable
+ private final CharSequence mSubtitle;
+
+ @Nullable
+ private final CharSequence mContentDescription;
+
+ @Nullable
+ private final PendingIntent mPendingIntent;
+
+ @Nullable
+ private final Intent mIntent;
+
+ @Nullable
+ private final UserHandle mUserHandle;
+
+ @Nullable
+ private Bundle mExtras;
+
+ SmartspaceAction(Parcel in) {
+ mId = in.readString();
+ mIcon = in.readTypedObject(Icon.CREATOR);
+ mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ mExtras = in.readTypedObject(Bundle.CREATOR);
+ }
+
+ private SmartspaceAction(
+ @NonNull String id,
+ @Nullable Icon icon,
+ @NonNull CharSequence title,
+ @Nullable CharSequence subtitle,
+ @Nullable CharSequence contentDescription,
+ @Nullable PendingIntent pendingIntent,
+ @Nullable Intent intent,
+ @Nullable UserHandle userHandle,
+ @Nullable Bundle extras) {
+ mId = Objects.requireNonNull(id);
+ mIcon = icon;
+ mTitle = Objects.requireNonNull(title);
+ mSubtitle = subtitle;
+ mContentDescription = contentDescription;
+ mPendingIntent = pendingIntent;
+ mIntent = intent;
+ mUserHandle = userHandle;
+ mExtras = extras;
+ }
+
+ /**
+ * Returns the unique id of this object.
+ */
+ public @NonNull String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns an icon representing the action.
+ */
+ public @Nullable Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns a title representing the action.
+ */
+ public @NonNull CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns a subtitle representing the action.
+ */
+ public @Nullable CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Returns a content description representing the action.
+ */
+ public @Nullable CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Returns the action intent.
+ */
+ public @Nullable PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /**
+ * Returns the intent.
+ */
+ public @Nullable Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Returns the user handle.
+ */
+ public @Nullable UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ /**
+ * Returns the extra bundle for this object.
+ */
+ @SuppressLint("NullableCollection")
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceAction)) return false;
+ SmartspaceAction that = (SmartspaceAction) o;
+ return mId.equals(that.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mId);
+ out.writeTypedObject(mIcon, flags);
+ TextUtils.writeToParcel(mTitle, out, flags);
+ TextUtils.writeToParcel(mSubtitle, out, flags);
+ TextUtils.writeToParcel(mContentDescription, out, flags);
+ out.writeTypedObject(mPendingIntent, flags);
+ out.writeTypedObject(mIntent, flags);
+ out.writeTypedObject(mUserHandle, flags);
+ out.writeBundle(mExtras);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceAction{"
+ + "mId='" + mId + '\''
+ + ", mIcon=" + mIcon
+ + ", mTitle=" + mTitle
+ + ", mSubtitle=" + mSubtitle
+ + ", mContentDescription=" + mContentDescription
+ + ", mPendingIntent=" + mPendingIntent
+ + ", mIntent=" + mIntent
+ + ", mUserHandle=" + mUserHandle
+ + ", mExtras=" + mExtras
+ + '}';
+ }
+
+ public static final @NonNull Creator<SmartspaceAction> CREATOR =
+ new Creator<SmartspaceAction>() {
+ public SmartspaceAction createFromParcel(Parcel in) {
+ return new SmartspaceAction(in);
+ }
+ public SmartspaceAction[] newArray(int size) {
+ return new SmartspaceAction[size];
+ }
+ };
+
+ /**
+ * A builder for Smartspace action object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @NonNull
+ private String mId;
+
+ @Nullable
+ private Icon mIcon;
+
+ @NonNull
+ private CharSequence mTitle;
+
+ @Nullable
+ private CharSequence mSubtitle;
+
+ @Nullable
+ private CharSequence mContentDescription;
+
+ @Nullable
+ private PendingIntent mPendingIntent;
+
+ @Nullable
+ private Intent mIntent;
+
+ @Nullable
+ private UserHandle mUserHandle;
+
+ @Nullable
+ private Bundle mExtras;
+
+ /**
+ * Id and title are required.
+ */
+ public Builder(@NonNull String id, @NonNull String title) {
+ mId = Objects.requireNonNull(id);
+ mTitle = Objects.requireNonNull(title);
+ }
+
+ /**
+ * Sets the icon.
+ */
+ @NonNull
+ public Builder setIcon(
+ @Nullable Icon icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the subtitle.
+ */
+ @NonNull
+ public Builder setSubtitle(
+ @Nullable CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Sets the content description.
+ */
+ @NonNull
+ public Builder setContentDescription(
+ @Nullable CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Sets the pending intent.
+ */
+ @NonNull
+ public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the user handle.
+ */
+ @NonNull
+ public Builder setUserHandle(@Nullable UserHandle userHandle) {
+ mUserHandle = userHandle;
+ return this;
+ }
+
+ /**
+ * Sets the intent.
+ */
+ @NonNull
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the extra.
+ */
+ @NonNull
+ public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceAction instance.
+ *
+ * @throws IllegalStateException if no target is set
+ */
+ @NonNull
+ public SmartspaceAction build() {
+ return new SmartspaceAction(mId, mIcon, mTitle, mSubtitle, mContentDescription,
+ mPendingIntent, mIntent, mUserHandle, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/app/smartspace/SmartspaceConfig.aidl
similarity index 74%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/app/smartspace/SmartspaceConfig.aidl
index 284a4ba..136b6f4 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/app/smartspace/SmartspaceConfig.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.app.smartspace;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable SmartspaceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.java b/core/java/android/app/smartspace/SmartspaceConfig.java
new file mode 100644
index 0000000..07d7bf0
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceConfig} instance is supposed to be created by a smartspace client for each
+ * UISurface. The client can specify some initialization conditions for the UISurface like its name,
+ * expected number of smartspace cards etc. The clients can also specify if they want periodic
+ * updates or their desired maximum refresh frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceConfig implements Parcelable {
+
+ /**
+ * The least number of smartspace targets expected to be predicted by the backend. The backend
+ * will always try to satisfy this threshold but it is not guaranteed to always meet it.
+ */
+ private final int mSmartspaceTargetCount;
+
+ /**
+ * A {@link mUiSurface} is the name of the surface which will be used to display the cards. A
+ * few examples are homescreen, lockscreen, aod etc.
+ */
+ @NonNull
+ private final String mUiSurface;
+
+ /** Package name of the client. */
+ @NonNull
+ private String mPackageName;
+
+ /** Send other client UI configurations in extras.
+ *
+ * This can include:
+ *
+ * - Desired maximum update frequency
+ * - Request to get periodic updates
+ * - Request to support multiple clients for the same UISurface.
+ */
+ @Nullable
+ private final Bundle mExtras;
+
+ private SmartspaceConfig(@NonNull String uiSurface, int numPredictedTargets,
+ @NonNull String packageName, @Nullable Bundle extras) {
+ mUiSurface = uiSurface;
+ mSmartspaceTargetCount = numPredictedTargets;
+ mPackageName = packageName;
+ mExtras = extras;
+ }
+
+ private SmartspaceConfig(Parcel parcel) {
+ mUiSurface = parcel.readString();
+ mSmartspaceTargetCount = parcel.readInt();
+ mPackageName = parcel.readString();
+ mExtras = parcel.readBundle();
+ }
+
+ /** Returns the package name of the prediction context. */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /** Returns the number of smartspace targets requested by the user. */
+ @NonNull
+ public int getSmartspaceTargetCount() {
+ return mSmartspaceTargetCount;
+ }
+
+ /** Returns the UISurface requested by the client. */
+ @NonNull
+ public String getUiSurface() {
+ return mUiSurface;
+ }
+
+ @Nullable
+ @SuppressLint("NullableCollection")
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mUiSurface);
+ dest.writeInt(mSmartspaceTargetCount);
+ dest.writeString(mPackageName);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SmartspaceConfig that = (SmartspaceConfig) o;
+ return mSmartspaceTargetCount == that.mSmartspaceTargetCount
+ && Objects.equals(mUiSurface, that.mUiSurface)
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mExtras, that.mExtras);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSmartspaceTargetCount, mUiSurface, mPackageName, mExtras);
+ }
+
+ /**
+ * @see Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceConfig> CREATOR =
+ new Creator<SmartspaceConfig>() {
+ public SmartspaceConfig createFromParcel(Parcel parcel) {
+ return new SmartspaceConfig(parcel);
+ }
+
+ public SmartspaceConfig[] newArray(int size) {
+ return new SmartspaceConfig[size];
+ }
+ };
+
+ /**
+ * A builder for {@link SmartspaceConfig}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @NonNull
+ private int mSmartspaceTargetCount = 5; // Default count is 5
+ @NonNull
+ private final String mUiSurface;
+ @NonNull
+ private final String mPackageName;
+ @NonNull
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * @param context The {@link Context} which is used to fetch the package name.
+ * @param uiSurface the UI Surface name associated with this context.
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull Context context, @NonNull String uiSurface) {
+ mPackageName = context.getPackageName();
+ this.mUiSurface = uiSurface;
+ }
+
+ /**
+ * Used to set the expected number of cards for this context.
+ */
+ @NonNull
+ public Builder setSmartspaceTargetCount(int smartspaceTargetCount) {
+ this.mSmartspaceTargetCount = smartspaceTargetCount;
+ return this;
+ }
+
+ /**
+ * Used to send a bundle containing extras for the {@link SmartspaceConfig}.
+ */
+ @NonNull
+ public Builder setExtras(@SuppressLint("NullableCollection") @NonNull Bundle extras) {
+ this.mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Returns an instance of {@link SmartspaceConfig}.
+ */
+ @NonNull
+ public SmartspaceConfig build() {
+ return new SmartspaceConfig(mUiSurface, mSmartspaceTargetCount, mPackageName, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceManager.java b/core/java/android/app/smartspace/SmartspaceManager.java
new file mode 100644
index 0000000..ff5eb10
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import java.util.Objects;
+
+/**
+ * Smartspace is a container in Android which is used to show contextual content powered by the
+ * intelligence service running on the device. A smartspace container can be on AoD, lockscreen or
+ * on the homescreen and can show personalized cards which are either derived from on device or
+ * online signals.
+ *
+ * {@link SmartspaceManager} is a system service that provides methods to create Smartspace session
+ * clients. An instance of this class is returned when a client calls
+ * <code> context.getSystemService("smartspace"); </code>.
+ *
+ * After receiving the service, a client must call
+ * {@link SmartspaceManager#createSmartspaceSession(SmartspaceConfig)} with a corresponding
+ * {@link SmartspaceConfig} to get an instance of {@link SmartspaceSession}.
+ * This session is then a client's point of contact with the api. They can send events, request for
+ * updates using the session. It is client's duty to call {@link SmartspaceSession#destroy()} to
+ * destroy the session once they no longer need it.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceManager {
+
+ private final Context mContext;
+
+ /**
+ * @hide
+ */
+ public SmartspaceManager(Context context) {
+ mContext = Objects.requireNonNull(context);
+ }
+
+ /**
+ * Creates a new Smartspace session.
+ */
+ @NonNull
+ public SmartspaceSession createSmartspaceSession(
+ @NonNull SmartspaceConfig smartspaceConfig) {
+ return new SmartspaceSession(mContext, smartspaceConfig);
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
new file mode 100644
index 0000000..16def61
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.smartspace;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.smartspace.ISmartspaceCallback.Stub;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * Client API to share information about the Smartspace UI state and execute query.
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ * private SmartspaceSession mSmartspaceSession;
+ *
+ * void onCreate() {
+ * mSmartspaceSession = mSmartspaceManager.createSmartspaceSession(smartspaceConfig)
+ * mSmartspaceSession.registerSmartspaceUpdates(...)
+ * }
+ *
+ * void onStart() {
+ * mSmartspaceSession.requestSmartspaceUpdate()
+ * }
+ *
+ * void onTouch(...) OR
+ * void onStateTransitionStarted(...) OR
+ * void onResume(...) OR
+ * void onStop(...) {
+ * mSmartspaceSession.notifyEvent(event);
+ * }
+ *
+ * void onDestroy() {
+ * mSmartspaceSession.unregisterPredictionUpdates()
+ * mSmartspaceSession.destroy();
+ * }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSession implements AutoCloseable {
+
+ private static final String TAG = SmartspaceSession.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private final android.app.smartspace.ISmartspaceManager mInterface;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ private final SmartspaceSessionId mSessionId;
+ private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+ private final IBinder mToken = new Binder();
+
+ /**
+ * Creates a new Smartspace ui client.
+ * <p>
+ * The caller should call {@link SmartspaceSession#destroy()} to dispose the client once it
+ * no longer used.
+ *
+ * @param context the {@link Context} of the user of this {@link SmartspaceSession}.
+ * @param smartspaceConfig the Smartspace context.
+ */
+ // b/177858121 Create weak reference child objects to not leak context.
+ SmartspaceSession(@NonNull Context context, @NonNull SmartspaceConfig smartspaceConfig) {
+ IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+ mInterface = android.app.smartspace.ISmartspaceManager.Stub.asInterface(b);
+ mSessionId = new SmartspaceSessionId(
+ context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+ try {
+ mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to cerate Smartspace session", e);
+ e.rethrowFromSystemServer();
+ }
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Notifies the Smartspace service of a Smartspace target event.
+ *
+ * @param event The {@link SmartspaceTargetEvent} that represents the Smartspace target event.
+ */
+ public void notifySmartspaceEvent(@NonNull SmartspaceTargetEvent event) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ try {
+ mInterface.notifySmartspaceEvent(mSessionId, event);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify event", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests the smartspace service for an update.
+ */
+ public void requestSmartspaceUpdate() {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ try {
+ mInterface.requestSmartspaceUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request update.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests the smartspace service provide continuous updates of smartspace cards via the
+ * provided callback, until the given callback is unregistered.
+ *
+ * @param callbackExecutor The callback executor to use when calling the callback.
+ * @param callback The Callback to be called when updates of Smartspace targets are
+ * available.
+ */
+ public void registerSmartspaceUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Callback callback) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ if (mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback is already registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+ callback::onTargetsAvailable);
+ mRegisteredCallbacks.put(callback, callbackWrapper);
+ mInterface.registerSmartspaceUpdates(mSessionId, callbackWrapper);
+ mInterface.requestSmartspaceUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register for smartspace updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Requests the smartspace service to stop providing continuous updates to the provided
+ * callback until the callback is re-registered.
+ *
+ * @see {@link SmartspaceSession#registerSmartspaceUpdates(Executor, Callback)}.
+ *
+ * @param callback The callback to be unregistered.
+ */
+ public void unregisterSmartspaceUpdates(@NonNull Callback callback) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ if (!mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback was never registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+ mInterface.unregisterSmartspaceUpdates(mSessionId, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister for smartspace updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Destroys the client and unregisters the callback. Any method on this class after this call
+ * will throw {@link IllegalStateException}.
+ */
+ public void destroy() {
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+
+ // Do destroy;
+ try {
+ mInterface.destroySmartspaceSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify Smartspace target event", e);
+ e.rethrowFromSystemServer();
+ }
+ } else {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ if (!mIsClosed.get()) {
+ destroy();
+ }
+ } finally {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ finalize();
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ }
+
+ /**
+ * Callback for receiving smartspace updates.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new set of smartspace targets are available.
+ *
+ * @param targets Sorted list of smartspace targets.
+ */
+ void onTargetsAvailable(@NonNull List<SmartspaceTarget> targets);
+ }
+
+ static class CallbackWrapper extends Stub {
+
+ private final Consumer<List<SmartspaceTarget>> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<List<SmartspaceTarget>> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onResult(ParceledListSlice result) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
+ }
+ mExecutor.execute(() -> mCallback.accept(result.getList()));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
similarity index 74%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/app/smartspace/SmartspaceSessionId.aidl
index 284a4ba..a864412 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.app.smartspace;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable SmartspaceSessionId;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.java b/core/java/android/app/smartspace/SmartspaceSessionId.java
new file mode 100644
index 0000000..5220c35
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The id for an Smartspace session. See {@link SmartspaceSession}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSessionId implements Parcelable {
+
+ @NonNull
+ private final String mId;
+
+ @NonNull
+ private final int mUserId;
+
+ /**
+ * Creates a new id for a Smartspace session.
+ *
+ * @hide
+ */
+ public SmartspaceSessionId(@NonNull final String id, @NonNull final int userId) {
+ mId = id;
+ mUserId = userId;
+ }
+
+ private SmartspaceSessionId(Parcel p) {
+ mId = p.readString();
+ mUserId = p.readInt();
+ }
+
+ /**
+ * Returns a {@link String} Id of this sessionId.
+ */
+ @Nullable
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the userId associated with this sessionId.
+ */
+ @NonNull
+ public int getUserId() {
+ return mUserId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+ SmartspaceSessionId other = (SmartspaceSessionId) o;
+ return mId.equals(other.mId) && mUserId == other.mUserId;
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceSessionId{"
+ + "mId='" + mId + '\''
+ + ", mUserId=" + mUserId
+ + '}';
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mUserId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeInt(mUserId);
+ }
+
+ public static final @NonNull Creator<SmartspaceSessionId> CREATOR =
+ new Creator<SmartspaceSessionId>() {
+ public SmartspaceSessionId createFromParcel(Parcel parcel) {
+ return new SmartspaceSessionId(parcel);
+ }
+
+ public SmartspaceSessionId[] newArray(int size) {
+ return new SmartspaceSessionId[size];
+ }
+ };
+}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/app/smartspace/SmartspaceTarget.aidl
similarity index 74%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/app/smartspace/SmartspaceTarget.aidl
index 284a4ba..3442cf2 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/app/smartspace/SmartspaceTarget.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.app.smartspace;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable SmartspaceTarget;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
new file mode 100644
index 0000000..ce5040e
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTarget} is a data class which holds all properties necessary to inflate a
+ * smartspace card. It contains data and related metadata which is supposed to be utilized by
+ * smartspace clients based on their own UI/UX requirements. Some of the properties have
+ * {@link SmartspaceAction} as their type because they can have associated actions.
+ *
+ * <p><b>NOTE: </b>
+ * If {@link mWidgetId} is set, it should be preferred over all other properties.
+ * Else, if {@link mSliceUri} is set, it should be preferred over all other data properties.
+ * Otherwise, the instance should be treated as a data object.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTarget implements Parcelable {
+
+ /** A unique Id for an instance of {@link SmartspaceTarget}. */
+ @NonNull
+ private final String mSmartspaceTargetId;
+
+ /** A {@link SmartspaceAction} for the header in the Smartspace card. */
+ @Nullable
+ private final SmartspaceAction mHeaderAction;
+
+ /** A {@link SmartspaceAction} for the base action in the Smartspace card. */
+ @Nullable
+ private final SmartspaceAction mBaseAction;
+
+ /** A timestamp indicating when the card was created. */
+ @NonNull
+ private final long mCreationTimeMillis;
+
+ /**
+ * A timestamp indicating when the card should be removed from view, in case the service
+ * disconnects or restarts.
+ */
+ @NonNull
+ private final long mExpiryTimeMillis;
+
+ /** A score assigned to a target. */
+ @NonNull
+ private final float mScore;
+
+ /** A {@link List<SmartspaceAction>} containing all action chips. */
+ @NonNull
+ private final List<SmartspaceAction> mActionChips;
+
+ /** A {@link List<SmartspaceAction>} containing all icons for the grid. */
+ @NonNull
+ private final List<SmartspaceAction> mIconGrid;
+
+ /**
+ * {@link FeatureType} indicating the feature type of this card.
+ *
+ * @see FeatureType
+ */
+ @FeatureType
+ @NonNull
+ private final int mFeatureType;
+
+ /**
+ * Indicates whether the content is sensitive. Certain UI surfaces may choose to skip rendering
+ * real content until the device is unlocked.
+ */
+ @NonNull
+ private final boolean mSensitive;
+
+ /** Indicating if the UI should show this target in its expanded state. */
+ @NonNull
+ private final boolean mShouldShowExpanded;
+
+ /** A Notification key if the target was generated using a notification. */
+ @Nullable
+ private final String mSourceNotificationKey;
+
+ /** {@link ComponentName} for this target. */
+ @NonNull
+ private final ComponentName mComponentName;
+
+ /** {@link UserHandle} for this target. */
+ @NonNull
+ private final UserHandle mUserHandle;
+
+ /** Target Ids of other {@link SmartspaceTarget}s if they are associated with this target. */
+ @Nullable
+ private final String mAssociatedSmartspaceTargetId;
+
+ /** {@link Uri} Slice Uri if this target is a slice. */
+ @Nullable
+ private final Uri mSliceUri;
+
+ /** {@link AppWidgetProviderInfo} if this target is a widget. */
+ @Nullable
+ private final AppWidgetProviderInfo mWidgetId;
+
+ public static final int FEATURE_UNDEFINED = 0;
+ public static final int FEATURE_WEATHER = 1;
+ public static final int FEATURE_CALENDAR = 2;
+ public static final int FEATURE_COMMUTE_TIME = 3;
+ public static final int FEATURE_FLIGHT = 4;
+ public static final int FEATURE_TIPS = 5;
+ public static final int FEATURE_REMINDER = 6;
+ public static final int FEATURE_ALARM = 7;
+ public static final int FEATURE_ONBOARDING = 8;
+ public static final int FEATURE_SPORTS = 9;
+ public static final int FEATURE_WEATHER_ALERT = 10;
+ public static final int FEATURE_CONSENT = 11;
+ public static final int FEATURE_STOCK_PRICE_CHANGE = 12;
+ public static final int FEATURE_SHOPPING_LIST = 13;
+ public static final int FEATURE_LOYALTY_CARD = 14;
+ public static final int FEATURE_MEDIA = 15;
+ public static final int FEATURE_BEDTIME_ROUTINE = 16;
+ public static final int FEATURE_FITNESS_TRACKING = 17;
+ public static final int FEATURE_ETA_MONITORING = 18;
+ public static final int FEATURE_MISSED_CALL = 19;
+ public static final int FEATURE_PACKAGE_TRACKING = 20;
+ public static final int FEATURE_TIMER = 21;
+ public static final int FEATURE_STOPWATCH = 22;
+ public static final int FEATURE_UPCOMING_ALARM = 23;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"FEATURE_"}, value = {
+ FEATURE_UNDEFINED,
+ FEATURE_WEATHER,
+ FEATURE_CALENDAR,
+ FEATURE_COMMUTE_TIME,
+ FEATURE_FLIGHT,
+ FEATURE_TIPS,
+ FEATURE_REMINDER,
+ FEATURE_ALARM,
+ FEATURE_ONBOARDING,
+ FEATURE_SPORTS,
+ FEATURE_WEATHER_ALERT,
+ FEATURE_CONSENT,
+ FEATURE_STOCK_PRICE_CHANGE,
+ FEATURE_SHOPPING_LIST,
+ FEATURE_LOYALTY_CARD,
+ FEATURE_MEDIA,
+ FEATURE_BEDTIME_ROUTINE,
+ FEATURE_FITNESS_TRACKING,
+ FEATURE_ETA_MONITORING,
+ FEATURE_MISSED_CALL,
+ FEATURE_PACKAGE_TRACKING,
+ FEATURE_TIMER,
+ FEATURE_STOPWATCH,
+ FEATURE_UPCOMING_ALARM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FeatureType {
+ }
+
+ private SmartspaceTarget(Parcel in) {
+ this.mSmartspaceTargetId = in.readString();
+ this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
+ this.mBaseAction = in.readTypedObject(SmartspaceAction.CREATOR);
+ this.mCreationTimeMillis = in.readLong();
+ this.mExpiryTimeMillis = in.readLong();
+ this.mScore = in.readFloat();
+ this.mActionChips = in.createTypedArrayList(SmartspaceAction.CREATOR);
+ this.mIconGrid = in.createTypedArrayList(SmartspaceAction.CREATOR);
+ this.mFeatureType = in.readInt();
+ this.mSensitive = in.readBoolean();
+ this.mShouldShowExpanded = in.readBoolean();
+ this.mSourceNotificationKey = in.readString();
+ this.mComponentName = in.readTypedObject(ComponentName.CREATOR);
+ this.mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ this.mAssociatedSmartspaceTargetId = in.readString();
+ this.mSliceUri = in.readTypedObject(Uri.CREATOR);
+ this.mWidgetId = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+ }
+
+ private SmartspaceTarget(String smartspaceTargetId,
+ SmartspaceAction headerAction, SmartspaceAction baseAction, long creationTimeMillis,
+ long expiryTimeMillis, float score,
+ List<SmartspaceAction> actionChips,
+ List<SmartspaceAction> iconGrid, int featureType, boolean sensitive,
+ boolean shouldShowExpanded, String sourceNotificationKey,
+ ComponentName componentName, UserHandle userHandle,
+ String associatedSmartspaceTargetId, Uri sliceUri,
+ AppWidgetProviderInfo widgetId) {
+ mSmartspaceTargetId = smartspaceTargetId;
+ mHeaderAction = headerAction;
+ mBaseAction = baseAction;
+ mCreationTimeMillis = creationTimeMillis;
+ mExpiryTimeMillis = expiryTimeMillis;
+ mScore = score;
+ mActionChips = actionChips;
+ mIconGrid = iconGrid;
+ mFeatureType = featureType;
+ mSensitive = sensitive;
+ mShouldShowExpanded = shouldShowExpanded;
+ mSourceNotificationKey = sourceNotificationKey;
+ mComponentName = componentName;
+ mUserHandle = userHandle;
+ mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+ mSliceUri = sliceUri;
+ mWidgetId = widgetId;
+ }
+
+ /**
+ * Returns the Id of the target.
+ */
+ @NonNull
+ public String getSmartspaceTargetId() {
+ return mSmartspaceTargetId;
+ }
+
+ /**
+ * Returns the header action of the target.
+ */
+ @Nullable
+ public SmartspaceAction getHeaderAction() {
+ return mHeaderAction;
+ }
+
+ /**
+ * Returns the base action of the target.
+ */
+ @Nullable
+ public SmartspaceAction getBaseAction() {
+ return mBaseAction;
+ }
+
+ /**
+ * Returns the creation time of the target.
+ */
+ @NonNull
+ public long getCreationTimeMillis() {
+ return mCreationTimeMillis;
+ }
+
+ /**
+ * Returns the expiry time of the target.
+ */
+ @NonNull
+ public long getExpiryTimeMillis() {
+ return mExpiryTimeMillis;
+ }
+
+ /**
+ * Returns the score of the target.
+ */
+ @NonNull
+ public float getScore() {
+ return mScore;
+ }
+
+ /**
+ * Return the action chips of the target.
+ */
+ @NonNull
+ public List<SmartspaceAction> getActionChips() {
+ return mActionChips;
+ }
+
+ /**
+ * Return the icons of the target.
+ */
+ @NonNull
+ public List<SmartspaceAction> getIconGrid() {
+ return mIconGrid;
+ }
+
+ /**
+ * Returns the feature type of the target.
+ */
+ @NonNull
+ public int getFeatureType() {
+ return mFeatureType;
+ }
+
+ /**
+ * Returns whether the target is sensitive or not.
+ */
+ @NonNull
+ public boolean isSensitive() {
+ return mSensitive;
+ }
+
+ /**
+ * Returns whether the target should be shown in expanded state.
+ */
+ @NonNull
+ public boolean shouldShowExpanded() {
+ return mShouldShowExpanded;
+ }
+
+ /**
+ * Returns the source notification key of the target.
+ */
+ @Nullable
+ public String getSourceNotificationKey() {
+ return mSourceNotificationKey;
+ }
+
+ /**
+ * Returns the component name of the target.
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * Returns the user handle of the target.
+ */
+ @NonNull
+ public UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ /**
+ * Returns the id of a target associated with this instance.
+ */
+ @Nullable
+ public String getAssociatedSmartspaceTargetId() {
+ return mAssociatedSmartspaceTargetId;
+ }
+
+ /**
+ * Returns the slice uri, if the target is a slice.
+ */
+ @Nullable
+ public Uri getSliceUri() {
+ return mSliceUri;
+ }
+
+ /**
+ * Returns the AppWidgetProviderInfo, if the target is a widget.
+ */
+ @Nullable
+ public AppWidgetProviderInfo getWidgetId() {
+ return mWidgetId;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceTarget> CREATOR = new Creator<SmartspaceTarget>() {
+ @Override
+ public SmartspaceTarget createFromParcel(Parcel source) {
+ return new SmartspaceTarget(source);
+ }
+
+ @Override
+ public SmartspaceTarget[] newArray(int size) {
+ return new SmartspaceTarget[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(this.mSmartspaceTargetId);
+ dest.writeTypedObject(this.mHeaderAction, flags);
+ dest.writeTypedObject(this.mBaseAction, flags);
+ dest.writeLong(this.mCreationTimeMillis);
+ dest.writeLong(this.mExpiryTimeMillis);
+ dest.writeFloat(this.mScore);
+ dest.writeTypedList(this.mActionChips);
+ dest.writeTypedList(this.mIconGrid);
+ dest.writeInt(this.mFeatureType);
+ dest.writeBoolean(this.mSensitive);
+ dest.writeBoolean(this.mShouldShowExpanded);
+ dest.writeString(this.mSourceNotificationKey);
+ dest.writeTypedObject(this.mComponentName, flags);
+ dest.writeTypedObject(this.mUserHandle, flags);
+ dest.writeString(this.mAssociatedSmartspaceTargetId);
+ dest.writeTypedObject(this.mSliceUri, flags);
+ dest.writeTypedObject(this.mWidgetId, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceTarget{"
+ + "mSmartspaceTargetId='" + mSmartspaceTargetId + '\''
+ + ", mHeaderAction=" + mHeaderAction
+ + ", mBaseAction=" + mBaseAction
+ + ", mCreationTimeMillis=" + mCreationTimeMillis
+ + ", mExpiryTimeMillis=" + mExpiryTimeMillis
+ + ", mScore=" + mScore
+ + ", mActionChips=" + mActionChips
+ + ", mIconGrid=" + mIconGrid
+ + ", mFeatureType=" + mFeatureType
+ + ", mSensitive=" + mSensitive
+ + ", mShouldShowExpanded=" + mShouldShowExpanded
+ + ", mSourceNotificationKey='" + mSourceNotificationKey + '\''
+ + ", mComponentName=" + mComponentName
+ + ", mUserHandle=" + mUserHandle
+ + ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
+ + ", mSliceUri=" + mSliceUri
+ + ", mWidgetId=" + mWidgetId
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SmartspaceTarget that = (SmartspaceTarget) o;
+ return mCreationTimeMillis == that.mCreationTimeMillis
+ && mExpiryTimeMillis == that.mExpiryTimeMillis
+ && Float.compare(that.mScore, mScore) == 0
+ && mFeatureType == that.mFeatureType
+ && mSensitive == that.mSensitive
+ && mShouldShowExpanded == that.mShouldShowExpanded
+ && mSmartspaceTargetId.equals(that.mSmartspaceTargetId)
+ && Objects.equals(mHeaderAction, that.mHeaderAction)
+ && Objects.equals(mBaseAction, that.mBaseAction)
+ && Objects.equals(mActionChips, that.mActionChips)
+ && Objects.equals(mIconGrid, that.mIconGrid)
+ && Objects.equals(mSourceNotificationKey, that.mSourceNotificationKey)
+ && mComponentName.equals(that.mComponentName)
+ && mUserHandle.equals(that.mUserHandle)
+ && Objects.equals(mAssociatedSmartspaceTargetId,
+ that.mAssociatedSmartspaceTargetId)
+ && Objects.equals(mSliceUri, that.mSliceUri)
+ && Objects.equals(mWidgetId, that.mWidgetId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
+ mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
+ mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+ }
+
+ /**
+ * A builder for {@link SmartspaceTarget} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private final String mSmartspaceTargetId;
+ private SmartspaceAction mHeaderAction;
+ private SmartspaceAction mBaseAction;
+ private long mCreationTimeMillis;
+ private long mExpiryTimeMillis;
+ private float mScore;
+ private List<SmartspaceAction> mActionChips = new ArrayList<>();
+ private List<SmartspaceAction> mIconGrid = new ArrayList<>();
+ private int mFeatureType;
+ private boolean mSensitive;
+ private boolean mShouldShowExpanded;
+ private String mSourceNotificationKey;
+ private final ComponentName mComponentName;
+ private final UserHandle mUserHandle;
+ private String mAssociatedSmartspaceTargetId;
+ private Uri mSliceUri;
+ private AppWidgetProviderInfo mWidgetId;
+
+ /**
+ * A builder for {@link SmartspaceTarget}.
+ *
+ * @param smartspaceTargetId the id of this target
+ * @param componentName the componentName of this target
+ * @param userHandle the userHandle of this target
+ */
+ public Builder(@NonNull String smartspaceTargetId,
+ @NonNull ComponentName componentName, @NonNull UserHandle userHandle) {
+ this.mSmartspaceTargetId = smartspaceTargetId;
+ this.mComponentName = componentName;
+ this.mUserHandle = userHandle;
+ }
+
+ /**
+ * Sets the header action.
+ */
+ @NonNull
+ public Builder setHeaderAction(@NonNull SmartspaceAction headerAction) {
+ this.mHeaderAction = headerAction;
+ return this;
+ }
+
+ /**
+ * Sets the base action.
+ */
+ @NonNull
+ public Builder setBaseAction(@NonNull SmartspaceAction baseAction) {
+ this.mBaseAction = baseAction;
+ return this;
+ }
+
+ /**
+ * Sets the creation time.
+ */
+ @NonNull
+ public Builder setCreationTimeMillis(@NonNull long creationTimeMillis) {
+ this.mCreationTimeMillis = creationTimeMillis;
+ return this;
+ }
+
+ /**
+ * Sets the expiration time.
+ */
+ @NonNull
+ public Builder setExpiryTimeMillis(@NonNull long expiryTimeMillis) {
+ this.mExpiryTimeMillis = expiryTimeMillis;
+ return this;
+ }
+
+ /**
+ * Sets the score.
+ */
+ @NonNull
+ public Builder setScore(@NonNull float score) {
+ this.mScore = score;
+ return this;
+ }
+
+ /**
+ * Sets the action chips.
+ */
+ @NonNull
+ public Builder setActionChips(@NonNull List<SmartspaceAction> actionChips) {
+ this.mActionChips = actionChips;
+ return this;
+ }
+
+ /**
+ * Sets the icon grid.
+ */
+ @NonNull
+ public Builder setIconGrid(@NonNull List<SmartspaceAction> iconGrid) {
+ this.mIconGrid = iconGrid;
+ return this;
+ }
+
+ /**
+ * Sets the feature type.
+ */
+ @NonNull
+ public Builder setFeatureType(@NonNull int featureType) {
+ this.mFeatureType = featureType;
+ return this;
+ }
+
+ /**
+ * Sets whether the contents are sensitive.
+ */
+ @NonNull
+ public Builder setSensitive(@NonNull boolean sensitive) {
+ this.mSensitive = sensitive;
+ return this;
+ }
+
+ /**
+ * Sets whether to show the card as expanded.
+ */
+ @NonNull
+ public Builder setShouldShowExpanded(@NonNull boolean shouldShowExpanded) {
+ this.mShouldShowExpanded = shouldShowExpanded;
+ return this;
+ }
+
+ /**
+ * Sets the source notification key.
+ */
+ @NonNull
+ public Builder setSourceNotificationKey(@NonNull String sourceNotificationKey) {
+ this.mSourceNotificationKey = sourceNotificationKey;
+ return this;
+ }
+
+ /**
+ * Sets the associated smartspace target id.
+ */
+ @NonNull
+ public Builder setAssociatedSmartspaceTargetId(
+ @NonNull String associatedSmartspaceTargetId) {
+ this.mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+ return this;
+ }
+
+ /**
+ * Sets the slice uri.
+ *
+ * <p><b>NOTE: </b> If {@link mWidgetId} is also set, {@link mSliceUri} should be ignored.
+ */
+ @NonNull
+ public Builder setSliceUri(@NonNull Uri sliceUri) {
+ this.mSliceUri = sliceUri;
+ return this;
+ }
+
+ /**
+ * Sets the widget id.
+ *
+ * <p><b>NOTE: </b> If {@link mWidgetId} is set, all other @Nullable params should be
+ * ignored.
+ */
+ @NonNull
+ public Builder setWidgetId(@NonNull AppWidgetProviderInfo widgetId) {
+ this.mWidgetId = widgetId;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link SmartspaceTarget}.
+ *
+ * @throws IllegalStateException when non null fields are set as null.
+ */
+ @NonNull
+ public SmartspaceTarget build() {
+ if (mSmartspaceTargetId == null
+ || mComponentName == null
+ || mUserHandle == null) {
+ throw new IllegalStateException("Please assign a value to all @NonNull args.");
+ }
+ return new SmartspaceTarget(mSmartspaceTargetId,
+ mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
+ mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
+ mSourceNotificationKey, mComponentName, mUserHandle,
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+ }
+ }
+}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
similarity index 74%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
index 284a4ba..e797a9b 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.app.smartspace;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable SmartspaceTargetEvent;
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
new file mode 100644
index 0000000..1e0653d
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.smartspace;
+
+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;
+
+/**
+ * A representation of a smartspace event.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTargetEvent implements Parcelable {
+
+ /**
+ * User interacted with the target.
+ */
+ public static final int EVENT_TARGET_INTERACTION = 1;
+
+ /**
+ * Smartspace target was brought into view.
+ */
+ public static final int EVENT_TARGET_IN_VIEW = 2;
+ /**
+ * Smartspace target went out of view.
+ */
+ public static final int EVENT_TARGET_OUT_OF_VIEW = 3;
+ /**
+ * A dismiss action was issued by the user.
+ */
+ public static final int EVENT_TARGET_DISMISS = 4;
+ /**
+ * A block action was issued by the user.
+ */
+ public static final int EVENT_TARGET_BLOCK = 5;
+ /**
+ * The Ui surface came into view.
+ */
+ public static final int EVENT_UI_SURFACE_IN_VIEW = 6;
+ /**
+ * The Ui surface went out of view.
+ */
+ public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7;
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceTargetEvent> CREATOR =
+ new Creator<SmartspaceTargetEvent>() {
+ public SmartspaceTargetEvent createFromParcel(Parcel parcel) {
+ return new SmartspaceTargetEvent(parcel);
+ }
+
+ public SmartspaceTargetEvent[] newArray(int size) {
+ return new SmartspaceTargetEvent[size];
+ }
+ };
+
+ @Nullable
+ private final SmartspaceTarget mSmartspaceTarget;
+
+ @Nullable
+ private final String mSmartspaceActionId;
+
+ @EventType
+ private final int mEventType;
+
+ private SmartspaceTargetEvent(@Nullable SmartspaceTarget smartspaceTarget,
+ @Nullable String smartspaceActionId,
+ @EventType int eventType) {
+ mSmartspaceTarget = smartspaceTarget;
+ mSmartspaceActionId = smartspaceActionId;
+ mEventType = eventType;
+ }
+
+ private SmartspaceTargetEvent(Parcel parcel) {
+ mSmartspaceTarget = parcel.readParcelable(null);
+ mSmartspaceActionId = parcel.readString();
+ mEventType = parcel.readInt();
+ }
+
+ /**
+ * Get the {@link SmartspaceTarget} associated with this event.
+ */
+ @Nullable
+ public SmartspaceTarget getSmartspaceTarget() {
+ return mSmartspaceTarget;
+ }
+
+ /**
+ * Get the action id of the Smartspace Action associated with this event.
+ */
+ @Nullable
+ public String getSmartspaceActionId() {
+ return mSmartspaceActionId;
+ }
+
+ /**
+ * Get the {@link EventType} of this event.
+ */
+ @NonNull
+ @EventType
+ public int getEventType() {
+ return mEventType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mSmartspaceTarget, flags);
+ dest.writeString(mSmartspaceActionId);
+ dest.writeInt(mEventType);
+ }
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"EVENT_"}, value = {
+ EVENT_TARGET_INTERACTION,
+ EVENT_TARGET_IN_VIEW,
+ EVENT_TARGET_OUT_OF_VIEW,
+ EVENT_TARGET_DISMISS,
+ EVENT_TARGET_BLOCK,
+ EVENT_UI_SURFACE_IN_VIEW,
+ EVENT_UI_SURFACE_OUT_OF_VIEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {
+ }
+
+ /**
+ * A builder for {@link SmartspaceTargetEvent}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @EventType
+ private final int mEventType;
+ @Nullable
+ private SmartspaceTarget mSmartspaceTarget;
+ @Nullable
+ private String mSmartspaceActionId;
+
+ /**
+ * A builder for {@link SmartspaceTargetEvent}.
+ */
+ public Builder(@EventType int eventType) {
+ mEventType = eventType;
+ }
+
+ /**
+ * Sets the SmartspaceTarget for this event.
+ */
+ @NonNull
+ public Builder setSmartspaceTarget(@NonNull SmartspaceTarget smartspaceTarget) {
+ mSmartspaceTarget = smartspaceTarget;
+ return this;
+ }
+
+ /**
+ * Sets the Smartspace action id for this event.
+ */
+ @NonNull
+ public Builder setSmartspaceActionId(@NonNull String smartspaceActionId) {
+ mSmartspaceActionId = smartspaceActionId;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link SmartspaceTargetEvent} instance.
+ */
+ @NonNull
+ public SmartspaceTargetEvent build() {
+ return new SmartspaceTargetEvent(mSmartspaceTarget, mSmartspaceActionId, mEventType);
+ }
+ }
+}
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 1ddfe0d..1d5dc1d 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -28,7 +28,6 @@
import android.net.ConnectivityManager;
import android.net.DataUsageRequest;
import android.net.INetworkStatsService;
-import android.net.NetworkIdentity;
import android.net.NetworkStack;
import android.net.NetworkTemplate;
import android.net.netstats.provider.INetworkStatsProviderCallback;
@@ -47,6 +46,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.NetworkIdentityUtils;
import java.util.Objects;
@@ -628,7 +628,7 @@
default:
throw new IllegalArgumentException("Cannot create template for network type "
+ networkType + ", subscriberId '"
- + NetworkIdentity.scrubSubscriberId(subscriberId) + "'.");
+ + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + "'.");
}
return template;
}
diff --git a/core/java/android/bluetooth/le/AdvertiseData.java b/core/java/android/bluetooth/le/AdvertiseData.java
index 397326c..cec6580 100644
--- a/core/java/android/bluetooth/le/AdvertiseData.java
+++ b/core/java/android/bluetooth/le/AdvertiseData.java
@@ -44,7 +44,7 @@
@Nullable
private final List<ParcelUuid> mServiceUuids;
- @Nullable
+ @NonNull
private final List<ParcelUuid> mServiceSolicitationUuids;
private final SparseArray<byte[]> mManufacturerSpecificData;
@@ -77,7 +77,7 @@
/**
* Returns a list of service solicitation UUIDs within the advertisement that we invite to connect.
*/
- @Nullable
+ @NonNull
public List<ParcelUuid> getServiceSolicitationUuids() {
return mServiceSolicitationUuids;
}
@@ -221,7 +221,7 @@
public static final class Builder {
@Nullable
private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
- @Nullable
+ @NonNull
private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>();
private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 083ce96..102c98f 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -55,6 +55,8 @@
genBuilder = false)
public final class AssociationRequest implements Parcelable {
+ private static final String LOG_TAG = AssociationRequest.class.getSimpleName();
+
/**
* Device profile: watch.
*
@@ -115,13 +117,6 @@
mDeviceProfilePrivilegesDescription = desc;
}
- private void onConstructed() {
- if (mDeviceProfile != null
- && !Objects.equals(mDeviceProfile, DEVICE_PROFILE_WATCH)) {
- throw new IllegalArgumentException("Invalid device profile: " + mDeviceProfile);
- }
- }
-
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean isSingleDevice() {
@@ -252,7 +247,7 @@
this.mCallingPackage = callingPackage;
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
- onConstructed();
+ // onConstructed(); // You can define this method to get a callback
}
/**
@@ -386,7 +381,7 @@
this.mCallingPackage = callingPackage;
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
- onConstructed();
+ // onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
@@ -404,10 +399,10 @@
};
@DataClass.Generated(
- time = 1610132130920L,
+ time = 1611692924843L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
- inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\nprivate void onConstructed()\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)")
+ inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/AutofillOptions.java b/core/java/android/content/AutofillOptions.java
index 80a7b16..0581ed5 100644
--- a/core/java/android/content/AutofillOptions.java
+++ b/core/java/android/content/AutofillOptions.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.os.Parcel;
@@ -62,6 +63,7 @@
* List of allowlisted activities.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public ArraySet<ComponentName> whitelistedActivitiesForAugmentedAutofill;
/**
@@ -73,6 +75,7 @@
* The disabled Activities of the package. key is component name string, value is when they
* will be enabled.
*/
+ @SuppressLint("NullableCollection")
@Nullable
public ArrayMap<String, Long> disabledActivities;
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index ef49e02..c296bb5 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.os.Parcel;
@@ -73,6 +74,7 @@
* for all acitivites in the package).
*/
@Nullable
+ @SuppressLint("NullableCollection")
public final ArraySet<ComponentName> whitelistedComponents;
/**
@@ -96,6 +98,7 @@
*/
public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
int textChangeFlushingFrequencyMs, int logHistorySize,
+ @SuppressLint("NullableCollection")
@Nullable ArraySet<ComponentName> whitelistedComponents) {
this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
textChangeFlushingFrequencyMs, logHistorySize, whitelistedComponents);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 125b5ff..987de3f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
import android.annotation.UiContext;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.GameManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
@@ -4625,6 +4626,18 @@
public static final String SEARCH_UI_SERVICE = "search_ui";
/**
+ * Used for getting the smartspace service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(SMARTSPACE_SERVICE)} should check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String SMARTSPACE_SERVICE = "smartspace";
+
+ /**
* Use with {@link #getSystemService(String)} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
@@ -5429,7 +5442,7 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.graphics.GameManager}.
+ * {@link GameManager}.
*
* @see #getSystemService(String)
*
@@ -5438,6 +5451,15 @@
public static final String GAME_SERVICE = "game";
/**
+ * Use with {@link #getSystemService(String)} to access domain verification service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -6154,6 +6176,7 @@
@UiContext
@NonNull
public Context createWindowContext(@NonNull Display display, @WindowType int type,
+ @SuppressLint("NullableCollection")
@Nullable Bundle options) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1752b48..30b2404 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -37,6 +37,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -2841,10 +2842,28 @@
* </p>
*
* @hide
+ * @deprecated Superseded by domain verification APIs. See {@link DomainVerificationManager}.
+ */
+ @Deprecated
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION =
+ "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+
+
+ /**
+ * Broadcast Action: Sent to the system domain verification agent when an app's domains need
+ * to be verified. The data contains the domains hosts to be verified against.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
*/
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+ public static final String ACTION_DOMAINS_NEED_VERIFICATION =
+ "android.intent.action.DOMAINS_NEED_VERIFICATION";
/**
* Broadcast Action: Resources for a set of packages (which were
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index e32068f..6ec1169 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -38,6 +38,8 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForBoolean;
import com.android.server.SystemConfig;
import java.lang.annotation.Retention;
@@ -56,6 +58,8 @@
* <application> tag.
*/
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
+ private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
+
/**
* Default task affinity of all activities in this application. See
* {@link ActivityInfo#taskAffinity} for more information. This comes
@@ -1336,6 +1340,51 @@
private @GwpAsanMode int gwpAsanMode;
/**
+ * Default (unspecified) setting of Memtag.
+ */
+ public static final int MEMTAG_DEFAULT = -1;
+
+ /**
+ * Do not enable Memtag in this application or process.
+ */
+ public static final int MEMTAG_OFF = 0;
+
+ /**
+ * Enable Memtag in Async mode in this application or process.
+ */
+ public static final int MEMTAG_ASYNC = 1;
+
+ /**
+ * Enable Memtag in Sync mode in this application or process.
+ */
+ public static final int MEMTAG_SYNC = 2;
+
+ /**
+ * These constants need to match the values of memtagMode in application manifest.
+ * @hide
+ */
+ @IntDef(prefix = {"MEMTAG_"}, value = {
+ MEMTAG_DEFAULT,
+ MEMTAG_OFF,
+ MEMTAG_ASYNC,
+ MEMTAG_SYNC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MemtagMode {}
+
+ /**
+ * Indicates if the application has requested Memtag to be enabled, disabled, or left
+ * unspecified. Processes can override this setting.
+ */
+ private @MemtagMode int memtagMode;
+
+ /**
+ * Enable automatic zero-initialization of native heap memory allocations.
+ */
+ @Nullable
+ private Boolean nativeHeapZeroInit;
+
+ /**
* Represents the default policy. The actual policy used will depend on other properties of
* the application, e.g. the target SDK version.
* @hide
@@ -1479,6 +1528,12 @@
if (gwpAsanMode != GWP_ASAN_DEFAULT) {
pw.println(prefix + "gwpAsanMode=" + gwpAsanMode);
}
+ if (memtagMode != MEMTAG_DEFAULT) {
+ pw.println(prefix + "memtagMode=" + memtagMode);
+ }
+ if (nativeHeapZeroInit != null) {
+ pw.println(prefix + "nativeHeapZeroInit=" + nativeHeapZeroInit);
+ }
}
super.dumpBack(pw, prefix);
}
@@ -1580,6 +1635,12 @@
if (gwpAsanMode != GWP_ASAN_DEFAULT) {
proto.write(ApplicationInfoProto.Detail.ENABLE_GWP_ASAN, gwpAsanMode);
}
+ if (memtagMode != MEMTAG_DEFAULT) {
+ proto.write(ApplicationInfoProto.Detail.ENABLE_MEMTAG, memtagMode);
+ }
+ if (nativeHeapZeroInit != null) {
+ proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT, nativeHeapZeroInit);
+ }
proto.end(detailToken);
}
proto.end(token);
@@ -1690,6 +1751,8 @@
hiddenUntilInstalled = orig.hiddenUntilInstalled;
zygotePreloadName = orig.zygotePreloadName;
gwpAsanMode = orig.gwpAsanMode;
+ memtagMode = orig.memtagMode;
+ nativeHeapZeroInit = orig.nativeHeapZeroInit;
}
public String toString() {
@@ -1774,6 +1837,8 @@
dest.writeInt(hiddenUntilInstalled ? 1 : 0);
dest.writeString8(zygotePreloadName);
dest.writeInt(gwpAsanMode);
+ dest.writeInt(memtagMode);
+ sForBoolean.parcel(nativeHeapZeroInit, dest, parcelableFlags);
}
public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1855,6 +1920,8 @@
hiddenUntilInstalled = source.readInt() != 0;
zygotePreloadName = source.readString8();
gwpAsanMode = source.readInt();
+ memtagMode = source.readInt();
+ nativeHeapZeroInit = sForBoolean.unparcel(source);
}
/**
@@ -2237,6 +2304,8 @@
/** {@hide} */ public void setBaseResourcePath(String baseResourcePath) { publicSourceDir = baseResourcePath; }
/** {@hide} */ public void setSplitResourcePaths(String[] splitResourcePaths) { splitPublicSourceDirs = splitResourcePaths; }
/** {@hide} */ public void setGwpAsanMode(@GwpAsanMode int value) { gwpAsanMode = value; }
+ /** {@hide} */ public void setMemtagMode(@MemtagMode int value) { memtagMode = value; }
+ /** {@hide} */ public void setNativeHeapZeroInit(@Nullable Boolean value) { nativeHeapZeroInit = value; }
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2250,4 +2319,8 @@
/** {@hide} */ public String[] getSplitResourcePaths() { return splitPublicSourceDirs; }
@GwpAsanMode
public int getGwpAsanMode() { return gwpAsanMode; }
+ @MemtagMode
+ public int getMemtagMode() { return memtagMode; }
+ @Nullable
+ public Boolean isNativeHeapZeroInit() { return nativeHeapZeroInit; }
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 34d1003..b8829bb 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -627,9 +627,13 @@
void verifyPendingInstall(int id, int verificationCode);
void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
+ /** @deprecated */
void verifyIntentFilter(int id, int verificationCode, in List<String> failedDomains);
+ /** @deprecated */
int getIntentVerificationStatus(String packageName, int userId);
+ /** @deprecated */
boolean updateIntentVerificationStatus(String packageName, int status, int userId);
+ /** @deprecated */
ParceledListSlice getIntentFilterVerifications(String packageName);
ParceledListSlice getAllIntentFilters(String packageName);
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index f991306..e8ef077 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -222,7 +222,7 @@
* <attribution>} tags included under <manifest>, or null if there were none. This
* is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS} was set.
*/
- @SuppressWarnings("ArrayReturn")
+ @SuppressWarnings({"ArrayReturn", "NullableCollection"})
public @Nullable Attribution[] attributions;
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9085ed2..d09d83f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,19 +47,15 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.dex.ArtManager;
-import android.content.pm.parsing.PackageInfoWithoutStateUtils;
-import android.content.pm.parsing.ParsingPackage;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.result.ParseInput;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
@@ -75,6 +71,12 @@
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.permission.PermissionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.GbaService;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDelegateManager;
import android.util.AndroidException;
import android.util.Log;
@@ -83,7 +85,6 @@
import dalvik.system.VMRuntime;
-import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.cert.Certificate;
@@ -93,6 +94,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
/**
* Class for retrieving various kinds of information related to the application
@@ -125,6 +127,19 @@
}
/**
+ * <application> level {@link android.content.pm.PackageManager.Property} tag specifying
+ * the XML resource ID containing an application's media capabilities XML file
+ *
+ * For example:
+ * <application>
+ * <property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
+ * android:resource="@xml/media_capabilities">
+ * <application>
+ */
+ public static final String PROPERTY_MEDIA_CAPABILITIES =
+ "android.media.PROPERTY_MEDIA_CAPABILITIES";
+
+ /**
* A property value set within the manifest.
* <p>
* The value of a property will only have a single type, as defined by
@@ -2188,8 +2203,10 @@
* {@link PackageManager#verifyIntentFilter} to indicate that the calling
* IntentFilter Verifier confirms that the IntentFilter is verified.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1;
@@ -2198,16 +2215,20 @@
* {@link PackageManager#verifyIntentFilter} to indicate that the calling
* IntentFilter Verifier confirms that the IntentFilter is NOT verified.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1;
/**
* Internal status code to indicate that an IntentFilter verification result is not specified.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
@@ -2217,8 +2238,10 @@
* will always be prompted the Intent Disambiguation Dialog if there are two
* or more Intent resolved for the IntentFilter's domain(s).
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
@@ -2229,8 +2252,10 @@
* or more resolution of the Intent. The default App for the domain(s)
* specified in the IntentFilter will also ALWAYS be used.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
@@ -2241,8 +2266,10 @@
* Intent resolved. The default App for the domain(s) specified in the
* IntentFilter will also NEVER be presented to the User.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
@@ -2255,8 +2282,10 @@
* more than one candidate app, then a disambiguation is *always* presented
* even if there is another candidate app with the 'always' state.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
@@ -2929,6 +2958,37 @@
public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports a single IMS registration as defined by carrier networks in the IMS service
+ * implementation using the {@link ImsService} API, {@link GbaService} API, and IRadio 1.6 HAL.
+ * <p>
+ * When set, the device must fully support the following APIs for an application to implement
+ * IMS single registration:
+ * <ul>
+ * <li> Updating RCS provisioning status using the {@link ProvisioningManager} API to supply an
+ * RCC.14 defined XML and notify IMS applications of Auto Configuration Server (ACS) or
+ * proprietary server provisioning updates.</li>
+ * <li>Opening a delegate in the device IMS service to forward SIP traffic to the carrier's
+ * network using the {@link SipDelegateManager} API</li>
+ * <li>Listening to EPS dedicated bearer establishment via the
+ * {@link ConnectivityManager#registerQosCallback}
+ * API to indicate to the application when to start/stop media traffic.</li>
+ * <li>Implementing Generic Bootstrapping Architecture (GBA) and providing the associated
+ * authentication keys to applications
+ * requesting this information via the {@link TelephonyManager#bootstrapAuthenticationRequest}
+ * API</li>
+ * <li>Implementing RCS User Capability Exchange using the {@link RcsUceAdapter} API</li>
+ * </ul>
+ * <p>
+ * This feature should only be defined if {@link #FEATURE_TELEPHONY_IMS} is also defined.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION =
+ "android.hardware.telephony.ims.singlereg";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device is capable of communicating with
* other devices via ultra wideband.
@@ -3582,6 +3642,24 @@
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * a Keystore implementation that can only enforce limited use key in hardware with max usage
+ * count equals to 1.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY =
+ "android.hardware.keystore.single_use_key";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * a Keystore implementation that can enforce limited use key in hardware with any max usage
+ * count (including count equals to 1).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
+ "android.hardware.keystore.limited_use_key";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
@@ -3681,8 +3759,10 @@
* Passed to an intent filter verifier and is used to call back to
* {@link #verifyIntentFilter}
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID";
@@ -3692,8 +3772,10 @@
*
* Usually this is "https"
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME";
@@ -3704,8 +3786,10 @@
*
* This is a space delimited list of hosts.
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS";
@@ -3715,8 +3799,10 @@
* from the hosts. Each host response will need to include the package name of APK containing
* the intent filter.
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME";
@@ -5490,7 +5576,7 @@
*
* @hide
*/
- @SuppressWarnings("HiddenAbstractMethod")
+ @SuppressWarnings({"HiddenAbstractMethod", "NullableCollection"})
@TestApi
public abstract @Nullable String[] getNamesForUids(int[] uids);
@@ -6778,25 +6864,8 @@
@Nullable
public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
@PackageInfoFlags int flags) {
- if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
- // Caller expressed no opinion about what encryption
- // aware/unaware components they want to see, so match both
- flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- }
-
- boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
- || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
-
- ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
- ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
- new File(archiveFilePath), 0, collectCertificates);
- if (result.isError()) {
- return null;
- }
- return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
- new PackageUserState(), UserHandle.getCallingUserId());
+ throw new UnsupportedOperationException(
+ "getPackageArchiveInfo() not implemented in subclass");
}
/**
@@ -6911,8 +6980,10 @@
* @throws SecurityException if the caller does not have the
* INTENT_FILTER_VERIFICATION_AGENT permission.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
@RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT)
@@ -6937,8 +7008,10 @@
* {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or
* {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED}
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)
@@ -6963,8 +7036,18 @@
*
* @return true if the status has been set. False otherwise.
*
+ * @deprecated This API represents a very dangerous behavior where Settings or a system app with
+ * the right permissions can force an application to be verified for all of its declared
+ * domains. This has been removed to prevent unintended usage, and no longer does anything,
+ * always returning false. If a caller truly wishes to grant <i></i>every</i> declared web
+ * domain to an application, use
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
+ * passing in all of the domains returned inside
+ * {@link DomainVerificationManager#getDomainVerificationUserSelection(String)}.
+ *
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
@RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
@@ -6981,8 +7064,10 @@
*
* @return a list of IntentFilterVerificationInfo for a specific package.
*
+ * @deprecated Use {@link DomainVerificationManager} instead.
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@NonNull
@SystemApi
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 9925871..5cc74c0 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -77,8 +77,6 @@
public boolean virtualPreload;
public int enabled;
public String lastDisableAppCaller;
- public int domainVerificationStatus;
- public int appLinkGeneration;
public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
public int installReason;
public @PackageManager.UninstallReason int uninstallReason;
@@ -100,8 +98,6 @@
hidden = false;
suspended = false;
enabled = COMPONENT_ENABLED_STATE_DEFAULT;
- domainVerificationStatus =
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
installReason = PackageManager.INSTALL_REASON_UNKNOWN;
uninstallReason = PackageManager.UNINSTALL_REASON_UNKNOWN;
}
@@ -120,8 +116,6 @@
virtualPreload = o.virtualPreload;
enabled = o.enabled;
lastDisableAppCaller = o.lastDisableAppCaller;
- domainVerificationStatus = o.domainVerificationStatus;
- appLinkGeneration = o.appLinkGeneration;
categoryHint = o.categoryHint;
installReason = o.installReason;
uninstallReason = o.uninstallReason;
@@ -416,12 +410,6 @@
&& !lastDisableAppCaller.equals(oldState.lastDisableAppCaller))) {
return false;
}
- if (domainVerificationStatus != oldState.domainVerificationStatus) {
- return false;
- }
- if (appLinkGeneration != oldState.appLinkGeneration) {
- return false;
- }
if (categoryHint != oldState.categoryHint) {
return false;
}
@@ -481,8 +469,6 @@
hashCode = 31 * hashCode + Boolean.hashCode(virtualPreload);
hashCode = 31 * hashCode + enabled;
hashCode = 31 * hashCode + Objects.hashCode(lastDisableAppCaller);
- hashCode = 31 * hashCode + domainVerificationStatus;
- hashCode = 31 * hashCode + appLinkGeneration;
hashCode = 31 * hashCode + categoryHint;
hashCode = 31 * hashCode + installReason;
hashCode = 31 * hashCode + uninstallReason;
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
index d45ff98..3dd5ee1 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -53,16 +53,30 @@
*/
public @ApplicationInfo.GwpAsanMode int gwpAsanMode;
+ /**
+ * Indicates if the process has requested Memtag to be enabled (in sync or async mode),
+ * disabled, or left unspecified.
+ */
+ public @ApplicationInfo.MemtagMode int memtagMode;
+
+ /**
+ * Enable automatic zero-initialization of native heap memory allocations.
+ */
+ @Nullable
+ public Boolean nativeHeapZeroInit;
+
@Deprecated
public ProcessInfo(@NonNull ProcessInfo orig) {
this.name = orig.name;
this.deniedPermissions = orig.deniedPermissions;
this.gwpAsanMode = orig.gwpAsanMode;
+ this.memtagMode = orig.memtagMode;
+ this.nativeHeapZeroInit = orig.nativeHeapZeroInit;
}
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -84,12 +98,19 @@
* If non-null, these are permissions that are not allowed in this process.
* @param gwpAsanMode
* Indicates if the process has requested GWP-ASan to be enabled, disabled, or left unspecified.
+ * @param memtagMode
+ * Indicates if the process has requested Memtag to be enabled (in sync or async mode),
+ * disabled, or left unspecified.
+ * @param nativeHeapZeroInit
+ * Enable automatic zero-initialization of native heap memory allocations.
*/
@DataClass.Generated.Member
public ProcessInfo(
@NonNull String name,
@Nullable ArraySet<String> deniedPermissions,
- @ApplicationInfo.GwpAsanMode int gwpAsanMode) {
+ @ApplicationInfo.GwpAsanMode int gwpAsanMode,
+ @ApplicationInfo.MemtagMode int memtagMode,
+ @Nullable Boolean nativeHeapZeroInit) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -97,6 +118,10 @@
this.gwpAsanMode = gwpAsanMode;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInit = nativeHeapZeroInit;
// onConstructed(); // You can define this method to get a callback
}
@@ -120,10 +145,13 @@
byte flg = 0;
if (deniedPermissions != null) flg |= 0x2;
+ if (nativeHeapZeroInit != null) flg |= 0x10;
dest.writeByte(flg);
dest.writeString(name);
sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
dest.writeInt(gwpAsanMode);
+ dest.writeInt(memtagMode);
+ if (nativeHeapZeroInit != null) dest.writeBoolean(nativeHeapZeroInit);
}
@Override
@@ -141,6 +169,8 @@
String _name = in.readString();
ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
int _gwpAsanMode = in.readInt();
+ int _memtagMode = in.readInt();
+ Boolean _nativeHeapZeroInit = (flg & 0x10) == 0 ? null : (Boolean) in.readBoolean();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -149,6 +179,10 @@
this.gwpAsanMode = _gwpAsanMode;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = _memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInit = _nativeHeapZeroInit;
// onConstructed(); // You can define this method to get a callback
}
@@ -168,10 +202,10 @@
};
@DataClass.Generated(
- time = 1584555730519L,
- codegenVersion = "1.0.15",
+ time = 1611614699049L,
+ codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java",
- inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.annotation.Nullable java.lang.Boolean nativeHeapZeroInit\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 8147599..7a01392 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -252,6 +252,10 @@
ParsingPackage setGwpAsanMode(int gwpAsanMode);
+ ParsingPackage setMemtagMode(int memtagMode);
+
+ ParsingPackage setNativeHeapZeroInit(@Nullable Boolean nativeHeapZeroInit);
+
ParsingPackage setCrossProfile(boolean crossProfile);
ParsingPackage setFullBackupContent(int fullBackupContent);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 51ec297..c1a93d8 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -380,6 +380,11 @@
private int autoRevokePermissions;
protected int gwpAsanMode;
+ protected int memtagMode;
+
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean nativeHeapZeroInit;
// TODO(chiuwinson): Non-null
@Nullable
@@ -1058,6 +1063,8 @@
appInfo.volumeUuid = volumeUuid;
appInfo.zygotePreloadName = zygotePreloadName;
appInfo.setGwpAsanMode(gwpAsanMode);
+ appInfo.setMemtagMode(memtagMode);
+ appInfo.setNativeHeapZeroInit(nativeHeapZeroInit);
appInfo.setBaseCodePath(mBaseApkPath);
appInfo.setBaseResourcePath(mBaseApkPath);
appInfo.setCodePath(mPath);
@@ -1190,6 +1197,8 @@
dest.writeSparseIntArray(this.minExtensionVersions);
dest.writeLong(this.mBooleans);
dest.writeMap(this.mProperties);
+ dest.writeInt(this.memtagMode);
+ sForBoolean.parcel(this.nativeHeapZeroInit, dest, flags);
}
public ParsingPackageImpl(Parcel in) {
@@ -1310,6 +1319,8 @@
this.minExtensionVersions = in.readSparseIntArray();
this.mBooleans = in.readLong();
this.mProperties = in.createTypedArrayMap(Property.CREATOR);
+ this.memtagMode = in.readInt();
+ this.nativeHeapZeroInit = sForBoolean.unparcel(in);
assignDerivedFields();
}
@@ -2062,6 +2073,17 @@
}
@Override
+ public int getMemtagMode() {
+ return memtagMode;
+ }
+
+ @Nullable
+ @Override
+ public Boolean isNativeHeapZeroInit() {
+ return nativeHeapZeroInit;
+ }
+
+ @Override
public boolean isPartiallyDirectBootAware() {
return getBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE);
}
@@ -2493,6 +2515,18 @@
}
@Override
+ public ParsingPackageImpl setMemtagMode(int value) {
+ memtagMode = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setNativeHeapZeroInit(@Nullable Boolean value) {
+ nativeHeapZeroInit = value;
+ return this;
+ }
+
+ @Override
public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) {
return setBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE, value);
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index a102e82..ff4cebd 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -874,6 +874,19 @@
*/
int getGwpAsanMode();
+ /**
+ * @see ApplicationInfo#memtagMode
+ * @see R.styleable#AndroidManifest_memtagMode
+ */
+ int getMemtagMode();
+
+ /**
+ * @see ApplicationInfo#nativeHeapZeroInit
+ * @see R.styleable#AndroidManifest_nativeHeapZeroInit
+ */
+ @Nullable
+ Boolean isNativeHeapZeroInit();
+
// TODO(b/135203078): Hide and enforce going through PackageInfoUtils
ApplicationInfo toAppInfoWithoutState();
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index b054304..66bdb9b 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -34,7 +34,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleableRes;
-import android.app.ActivityThread;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
@@ -222,13 +221,15 @@
private static final int MAX_FILE_NAME_SIZE = 223;
/**
- * @see #parseDefault(ParseInput, File, int, boolean)
+ * @see #parseDefault(ParseInput, File, int, List, boolean)
*/
@NonNull
public static ParseResult<ParsingPackage> parseDefaultOneTime(File file,
- @ParseFlags int parseFlags, boolean collectCertificates) {
+ @ParseFlags int parseFlags,
+ @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+ boolean collectCertificates) {
ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
- return parseDefault(input, file, parseFlags, collectCertificates);
+ return parseDefault(input, file, parseFlags, splitPermissions, collectCertificates);
}
/**
@@ -238,28 +239,32 @@
*/
@NonNull
public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
- @ParseFlags int parseFlags, boolean collectCertificates) {
+ @ParseFlags int parseFlags,
+ @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+ boolean collectCertificates) {
ParseResult<ParsingPackage> result;
- ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() {
- @Override
- public boolean hasFeature(String feature) {
- // Assume the device doesn't support anything. This will affect permission parsing
- // and will force <uses-permission/> declarations to include all requiredNotFeature
- // permissions and exclude all requiredFeature permissions. This mirrors the old
- // behavior.
- return false;
- }
+ ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, splitPermissions,
+ new Callback() {
+ @Override
+ public boolean hasFeature(String feature) {
+ // Assume the device doesn't support anything. This will affect permission
+ // parsing and will force <uses-permission/> declarations to include all
+ // requiredNotFeature permissions and exclude all requiredFeature
+ // permissions. This mirrors the old behavior.
+ return false;
+ }
- @Override
- public ParsingPackage startParsingPackage(
- @NonNull String packageName,
- @NonNull String baseApkPath,
- @NonNull String path,
- @NonNull TypedArray manifestArray, boolean isCoreApp) {
- return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray);
- }
- });
+ @Override
+ public ParsingPackage startParsingPackage(
+ @NonNull String packageName,
+ @NonNull String baseApkPath,
+ @NonNull String path,
+ @NonNull TypedArray manifestArray, boolean isCoreApp) {
+ return new ParsingPackageImpl(packageName, baseApkPath, path,
+ manifestArray);
+ }
+ });
try {
result = parser.parsePackage(input, file, parseFlags);
if (result.isError()) {
@@ -290,13 +295,18 @@
private boolean mOnlyCoreApps;
private String[] mSeparateProcesses;
private DisplayMetrics mDisplayMetrics;
+ @NonNull
+ private List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
private Callback mCallback;
public ParsingPackageUtils(boolean onlyCoreApps, String[] separateProcesses,
- DisplayMetrics displayMetrics, @NonNull Callback callback) {
+ DisplayMetrics displayMetrics,
+ @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+ @NonNull Callback callback) {
mOnlyCoreApps = onlyCoreApps;
mSeparateProcesses = separateProcesses;
mDisplayMetrics = displayMetrics;
+ mSplitPermissionInfos = splitPermissions;
mCallback = callback;
}
@@ -1974,6 +1984,11 @@
}
pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
+ pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1));
+ if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInit)) {
+ pkg.setNativeHeapZeroInit(sa.getBoolean(
+ R.styleable.AndroidManifestApplication_nativeHeapZeroInit, false));
+ }
} finally {
sa.recycle();
}
@@ -2483,6 +2498,10 @@
/**
* Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
+ *
+ * This is distinct from any of the functionality of app links domain verification, and cannot
+ * be converted to remain backwards compatible. It's possible the presence of this flag does
+ * not indicate a valid package for domain verification.
*/
private static boolean hasDomainURLs(ParsingPackage pkg) {
final List<ParsedActivity> activities = pkg.getActivities();
@@ -2743,14 +2762,10 @@
}
}
- private static void convertSplitPermissions(ParsingPackage pkg) {
- final List<PermissionManager.SplitPermissionInfo> splitPermissions =
- ActivityThread.currentApplication().getSystemService(PermissionManager.class)
- .getSplitPermissions();
-
- final int listSize = splitPermissions.size();
+ private void convertSplitPermissions(ParsingPackage pkg) {
+ final int listSize = mSplitPermissionInfos.size();
for (int is = 0; is < listSize; is++) {
- final PermissionManager.SplitPermissionInfo spi = splitPermissions.get(is);
+ final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
List<String> requestedPermissions = pkg.getRequestedPermissions();
if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
|| !requestedPermissions.contains(spi.getSplitPermission())) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index e0ae81b..89fef9d 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -41,7 +42,10 @@
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
protected Set<String> deniedPermissions = emptySet();
- protected int gwpAsanMode = -1;
+ protected int gwpAsanMode = ApplicationInfo.GWP_ASAN_DEFAULT;
+ protected int memtagMode = ApplicationInfo.MEMTAG_DEFAULT;
+ @Nullable
+ protected Boolean nativeHeapZeroInit = null;
public ParsedProcess() {
}
@@ -57,7 +61,7 @@
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -74,7 +78,9 @@
public ParsedProcess(
@NonNull String name,
@NonNull Set<String> deniedPermissions,
- int gwpAsanMode) {
+ int gwpAsanMode,
+ int memtagMode,
+ @Nullable Boolean nativeHeapZeroInit) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -82,6 +88,8 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deniedPermissions);
this.gwpAsanMode = gwpAsanMode;
+ this.memtagMode = memtagMode;
+ this.nativeHeapZeroInit = nativeHeapZeroInit;
// onConstructed(); // You can define this method to get a callback
}
@@ -102,6 +110,16 @@
}
@DataClass.Generated.Member
+ public int getMemtagMode() {
+ return memtagMode;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable Boolean getNativeHeapZeroInit() {
+ return nativeHeapZeroInit;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<Set<String>> sParcellingForDeniedPermissions =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedStringSet.class);
@@ -118,9 +136,14 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ byte flg = 0;
+ if (nativeHeapZeroInit != null) flg |= 0x10;
+ dest.writeByte(flg);
dest.writeString(name);
sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
dest.writeInt(gwpAsanMode);
+ dest.writeInt(memtagMode);
+ if (nativeHeapZeroInit != null) dest.writeBoolean(nativeHeapZeroInit);
}
@Override
@@ -134,9 +157,12 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ byte flg = in.readByte();
String _name = in.readString();
Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
int _gwpAsanMode = in.readInt();
+ int _memtagMode = in.readInt();
+ Boolean _nativeHeapZeroInit = (flg & 0x10) == 0 ? null : (Boolean) in.readBoolean();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -145,6 +171,8 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deniedPermissions);
this.gwpAsanMode = _gwpAsanMode;
+ this.memtagMode = _memtagMode;
+ this.nativeHeapZeroInit = _nativeHeapZeroInit;
// onConstructed(); // You can define this method to get a callback
}
@@ -164,10 +192,10 @@
};
@DataClass.Generated(
- time = 1584557524776L,
- codegenVersion = "1.0.15",
+ time = 1611615591258L,
+ codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java",
- inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprotected int gwpAsanMode\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprotected int gwpAsanMode\nprotected int memtagMode\nprotected @android.annotation.Nullable java.lang.Boolean nativeHeapZeroInit\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
index 9bff719..2579774 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -103,6 +103,11 @@
}
proc.gwpAsanMode = sa.getInt(R.styleable.AndroidManifestProcess_gwpAsanMode, -1);
+ proc.memtagMode = sa.getInt(R.styleable.AndroidManifestProcess_memtagMode, -1);
+ if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInit)) {
+ proc.nativeHeapZeroInit =
+ sa.getBoolean(R.styleable.AndroidManifestProcess_nativeHeapZeroInit, false);
+ }
} finally {
sa.recycle();
}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
similarity index 88%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
index 284a4ba..c143cc5 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.content.pm.verify.domain;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable DomainVerificationInfo;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
new file mode 100644
index 0000000..7afbe1f
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Contains the state of all domains for a given package on device. Used by the domain verification
+ * agent to determine the domains declared by a package that need to be verified by comparing
+ * against the digital asset links response from the server hosting that domain.
+ * <p>
+ * These values for each domain can be modified through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+ genEqualsHashCode = true)
+public final class DomainVerificationInfo implements Parcelable {
+
+ /**
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+ private final UUID mIdentifier;
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * Map of host names to their current state. State is an integer, which defaults to
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+ * domain verification agent (the intended consumer of this API), which can be equal
+ * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+ * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+ * any unsuccessful response.
+ * <p>
+ * Any value non-inclusive between those 2 values are reserved for use by the system.
+ * The domain verification agent may be able to act on these reserved values, and this
+ * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+ * It is expected that the agent attempt to verify all domains that it can modify the
+ * state of, even if it does not understand the meaning of those values.
+ */
+ @NonNull
+ private final Map<String, Integer> mHostToStateMap;
+
+
+
+ // 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/pm/verify/domain/DomainVerificationInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationInfo.
+ *
+ * @param identifier
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ * @param packageName
+ * The package name that this data corresponds to.
+ * @param hostToStateMap
+ * Map of host names to their current state. State is an integer, which defaults to
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+ * domain verification agent (the intended consumer of this API), which can be equal
+ * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+ * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+ * any unsuccessful response.
+ * <p>
+ * Any value non-inclusive between those 2 values are reserved for use by the system.
+ * The domain verification agent may be able to act on these reserved values, and this
+ * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+ * It is expected that the agent attempt to verify all domains that it can modify the
+ * state of, even if it does not understand the meaning of those values.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationInfo(
+ @NonNull UUID identifier,
+ @NonNull String packageName,
+ @NonNull Map<String,Integer> hostToStateMap) {
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull UUID getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Map of host names to their current state. State is an integer, which defaults to
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+ * domain verification agent (the intended consumer of this API), which can be equal
+ * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+ * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+ * any unsuccessful response.
+ * <p>
+ * Any value non-inclusive between those 2 values are reserved for use by the system.
+ * The domain verification agent may be able to act on these reserved values, and this
+ * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+ * It is expected that the agent attempt to verify all domains that it can modify the
+ * state of, even if it does not understand the meaning of those values.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,Integer> getHostToStateMap() {
+ return mHostToStateMap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationInfo { " +
+ "identifier = " + mIdentifier + ", " +
+ "packageName = " + mPackageName + ", " +
+ "hostToStateMap = " + mHostToStateMap +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationInfo that = (DomainVerificationInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap);
+ }
+
+ @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 + java.util.Objects.hashCode(mIdentifier);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<UUID> sParcellingForIdentifier =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForUUID.class);
+ static {
+ if (sParcellingForIdentifier == null) {
+ sParcellingForIdentifier = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForUUID());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+ dest.writeString(mPackageName);
+ dest.writeMap(mHostToStateMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationInfo(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ UUID identifier = sParcellingForIdentifier.unparcel(in);
+ String packageName = in.readString();
+ Map<String,Integer> hostToStateMap = new java.util.LinkedHashMap<>();
+ in.readMap(hostToStateMap, Integer.class.getClassLoader());
+
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationInfo> CREATOR
+ = new Parcelable.Creator<DomainVerificationInfo>() {
+ @Override
+ public DomainVerificationInfo[] newArray(int size) {
+ return new DomainVerificationInfo[size];
+ }
+
+ @Override
+ public DomainVerificationInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainVerificationInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1611862790369L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
new file mode 100644
index 0000000..af12536
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.UserHandle;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * System service to access the domain verification APIs.
+ *
+ * Allows the approved domain verification
+ * agent on the device (the sole holder of
+ * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status
+ * of domains declared by applications in their AndroidManifest.xml, to allow them to open those
+ * links inside the app when selected by the user. This is done through querying
+ * {@link #getDomainVerificationInfo(String)} and calling
+ * {@link #setDomainVerificationStatus(UUID, Set, int)}.
+ *
+ * Also allows the domain preference settings (holder of
+ * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the
+ * preferences of the user, when they have chosen to explicitly allow an application to open links.
+ * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling
+ * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
+ * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.DOMAIN_VERIFICATION_SERVICE)
+public interface DomainVerificationManager {
+
+ /**
+ * Extra field name for a {@link DomainVerificationRequest} for the requested packages.
+ * Passed to an the domain verification agent that handles
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}.
+ */
+ String EXTRA_VERIFICATION_REQUEST =
+ "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
+
+ /**
+ * No response has been recorded by either the system or any verification agent.
+ */
+ int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
+
+ /** The verification agent has explicitly verified the domain at some point. */
+ int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+
+ /**
+ * The first available custom response code. This and any greater integer, along with
+ * {@link #STATE_SUCCESS} are the only values settable by the verification agent. All values
+ * will be treated as if the domain is unverified.
+ */
+ int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+
+ /** @hide */
+ @NonNull
+ static String stateToDebugString(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ return "none";
+ case DomainVerificationState.STATE_SUCCESS:
+ return "verified";
+ case DomainVerificationState.STATE_APPROVED:
+ return "approved";
+ case DomainVerificationState.STATE_DENIED:
+ return "denied";
+ case DomainVerificationState.STATE_MIGRATED:
+ return "migrated";
+ case DomainVerificationState.STATE_RESTORED:
+ return "restored";
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return "legacy_failure";
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return "system_configured";
+ default:
+ return String.valueOf(state);
+ }
+ }
+
+ /**
+ * Checks if a state considers the corresponding domain to be successfully verified. The
+ * domain verification agent may use this to determine whether or not to re-verify a domain.
+ */
+ static boolean isStateVerified(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return true;
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a state is modifiable by the domain verification agent. This is useful as the
+ * platform may add new state codes in newer versions, and older verification agents can use
+ * this method to determine if a state can be changed without having to be aware of what the
+ * new state means.
+ */
+ static boolean isStateModifiable(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return true;
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return false;
+ default:
+ return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ }
+ }
+
+ /**
+ * For determine re-verify policy. This is hidden from the domain verification agent so that
+ * no behavior is made based on the result.
+ * @hide
+ */
+ static boolean isStateDefault(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ return true;
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is
+ * usually a heavy workload and should be done infrequently.
+ *
+ * @return the current snapshot of package names with valid autoVerify URLs.
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ List<String> getValidVerificationPackageNames();
+
+ /**
+ * Retrieves the domain verification state for a given package.
+ *
+ * @return the data for the package, or null if it does not declare any autoVerify domains
+ * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error
+ * and should not be re-tried except on a time scheduled basis.
+ */
+ @Nullable
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+ })
+ DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Change the verification status of the {@param domains} of the package associated with
+ * {@param domainSetId}.
+ *
+ * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+ * @param domains List of host names to change the state of.
+ * @param state See {@link DomainVerificationInfo#getHostToStateMap()}.
+ * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
+ * invalid. This usually means the work being processed by the
+ * verification agent is outdated and a new request should
+ * be scheduled, if one has not already been done as part of
+ * the {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}
+ * broadcast.
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is
+ * installed on a volume that is no longer mounted. This
+ * error is unrecoverable until the package is available
+ * again, and should not be re-tried except on a time
+ * scheduled basis.
+ */
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ @DomainVerificationState.State int state) throws NameNotFoundException;
+
+ /**
+ * TODO(b/178525735): This documentation is incorrect in the context of UX changes.
+ * Change whether the given {@param packageName} is allowed to automatically open verified
+ * HTTP/HTTPS domains. The final state is determined along with the verification status for the
+ * specific domain being opened and other system state. An app with this enabled is not
+ * guaranteed to be the sole link handler for its domains.
+ *
+ * By default, all apps are allowed to open verified links. Users must disable them explicitly.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed)
+ throws NameNotFoundException;
+
+ /**
+ * Update the recorded user selection for the given {@param domains} for the given {@param
+ * domainSetId}. This state is recorded for the lifetime of a domain for a package on device,
+ * and will never be reset by the system short of an app data clear.
+ *
+ * This state is stored per device user. If another user needs to be changed, the appropriate
+ * permissions must be acquired and
+ * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
+ *
+ * This will be combined with the verification status and other system state to determine which
+ * application is launched to handle an app link.
+ *
+ * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+ * @param domains The domains to toggle the state of.
+ * @param enabled Whether or not the app should automatically open the domains specified.
+ * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
+ * invalid.
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is
+ * installed on a volume that is no longer mounted. This
+ * error is unrecoverable until the package is available
+ * again, and should not be re-tried except on a time
+ * scheduled basis.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException;
+
+ /**
+ * Retrieve the user selection data for the given {@param packageName} and the current user.
+ * It is the responsibility of the caller to ensure that the
+ * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls.
+ *
+ * This state is stored per device user. If another user needs to be accessed, the appropriate
+ * permissions must be acquired and
+ * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
+ *
+ * @param packageName The app to query state for.
+ * @return the user selection verification data for the given package for the current user,
+ * or null if the package does not declare any HTTP/HTTPS domains.
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
+ * provided by the caller is no longer valid. This may be recoverable, and the caller should
+ * re-query the package name associated with the ID using
+ * {@link #getDomainVerificationInfo(String)} in order to check. If that also fails, then the
+ * package is no longer known to the device and thus all pending work for it should be dropped.
+ *
+ * @hide
+ */
+ class InvalidDomainSetException extends IllegalArgumentException {
+
+ public static final int REASON_ID_NULL = 1;
+ public static final int REASON_ID_INVALID = 2;
+ public static final int REASON_SET_NULL_OR_EMPTY = 3;
+ public static final int REASON_UNKNOWN_DOMAIN = 4;
+
+ /** @hide */
+ @IntDef({
+ REASON_ID_NULL,
+ REASON_ID_INVALID,
+ REASON_SET_NULL_OR_EMPTY,
+ REASON_UNKNOWN_DOMAIN
+ })
+ public @interface Reason {
+ }
+
+ public static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName,
+ @Reason int reason) {
+ switch (reason) {
+ case REASON_ID_NULL:
+ return "Domain set ID cannot be null";
+ case REASON_ID_INVALID:
+ return "Domain set ID " + domainSetId + " has been invalidated";
+ case REASON_SET_NULL_OR_EMPTY:
+ return "Domain set cannot be null or empty";
+ case REASON_UNKNOWN_DOMAIN:
+ return "Domain set contains value that was not declared by the target package "
+ + packageName;
+ default:
+ return "Unknown failure";
+ }
+ }
+
+ @Reason
+ private final int mReason;
+
+ @Nullable
+ private final UUID mDomainSetId;
+
+ @Nullable
+ private final String mPackageName;
+
+ /** @hide */
+ public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName,
+ @Reason int reason) {
+ super(buildMessage(domainSetId, packageName, reason));
+ mDomainSetId = domainSetId;
+ mPackageName = packageName;
+ mReason = reason;
+ }
+
+ @Nullable
+ public UUID getDomainSetId() {
+ return mDomainSetId;
+ }
+
+ @Nullable
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Reason
+ public int getReason() {
+ return mReason;
+ }
+ }
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
new file mode 100644
index 0000000..5938def
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @hide
+ */
+@SuppressWarnings("RedundantThrows")
+public class DomainVerificationManagerImpl implements DomainVerificationManager {
+
+ public static final int ERROR_INVALID_DOMAIN_SET = 1;
+ public static final int ERROR_NAME_NOT_FOUND = 2;
+
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_DOMAIN_SET,
+ ERROR_NAME_NOT_FOUND,
+ })
+ private @interface Error {
+ }
+
+ private final Context mContext;
+
+ private final IDomainVerificationManager mDomainVerificationManager;
+
+ public DomainVerificationManagerImpl(Context context,
+ IDomainVerificationManager domainVerificationManager) {
+ mContext = context;
+ mDomainVerificationManager = domainVerificationManager;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getValidVerificationPackageNames() {
+ try {
+ return mDomainVerificationManager.getValidVerificationPackageNames();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationInfo(packageName);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Override
+ public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ int state) throws IllegalArgumentException, NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
+ new ArrayList<>(domains), state);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, domainSetId);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed) throws NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
+ allowed, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Override
+ public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled)
+ throws IllegalArgumentException, NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
+ new ArrayList<>(domains), enabled, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, domainSetId);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName) throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationUserSelection(packageName,
+ mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
+ return rethrow(exception, domainSetId, null);
+ }
+
+ private Exception rethrow(Exception exception, @Nullable String packageName) {
+ return rethrow(exception, null, packageName);
+ }
+
+ private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
+ @Nullable String packageName) {
+ if (exception instanceof ServiceSpecificException) {
+ int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
+ if (packageName == null) {
+ packageName = exception.getMessage();
+ }
+
+ @Error int managerErrorCode = packedErrorCode & 0xFFFF;
+ switch (managerErrorCode) {
+ case ERROR_INVALID_DOMAIN_SET:
+ int errorSpecificCode = packedErrorCode >> 16;
+ return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
+ domainSetId, packageName, errorSpecificCode));
+ case ERROR_NAME_NOT_FOUND:
+ return new NameNotFoundException(packageName);
+ default:
+ return exception;
+ }
+ } else if (exception instanceof RemoteException) {
+ return ((RemoteException) exception).rethrowFromSystemServer();
+ } else {
+ return exception;
+ }
+ }
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
new file mode 100644
index 0000000..473abce
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Set;
+
+/**
+ * Request object sent in the {@link Intent} that's broadcast to the domain verification
+ * agent, retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}.
+ * <p>
+ * This contains the set of packages which have been invalidated and will require
+ * re-verification. The exact domains can be retrieved with
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)}
+ *
+ * @hide
+ */
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genHiddenConstructor = true, genAidl = false, genEqualsHashCode = true)
+@SystemApi
+public final class DomainVerificationRequest implements Parcelable {
+
+ /**
+ * The package names of the apps that need to be verified. The receiver should call
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+ * these values to get the actual set of domains that need to be acted on.
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class)
+ private final Set<String> mPackageNames;
+
+
+
+ // 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/pm/verify/domain/DomainVerificationRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationRequest.
+ *
+ * @param packageNames
+ * The package names of the apps that need to be verified. The receiver should call
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+ * these values to get the actual set of domains that need to be acted on.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationRequest(
+ @NonNull Set<String> packageNames) {
+ this.mPackageNames = packageNames;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageNames);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The package names of the apps that need to be verified. The receiver should call
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+ * these values to get the actual set of domains that need to be acted on.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Set<String> getPackageNames() {
+ return mPackageNames;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationRequest other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationRequest that = (DomainVerificationRequest) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPackageNames, that.mPackageNames);
+ }
+
+ @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 + java.util.Objects.hashCode(mPackageNames);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<Set<String>> sParcellingForPackageNames =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForStringSet.class);
+ static {
+ if (sParcellingForPackageNames == null) {
+ sParcellingForPackageNames = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForStringSet());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ sParcellingForPackageNames.parcel(mPackageNames, dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationRequest(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Set<String> packageNames = sParcellingForPackageNames.unparcel(in);
+
+ this.mPackageNames = packageNames;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageNames);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationRequest> CREATOR
+ = new Parcelable.Creator<DomainVerificationRequest>() {
+ @Override
+ public DomainVerificationRequest[] newArray(int size) {
+ return new DomainVerificationRequest[size];
+ }
+
+ @Override
+ public DomainVerificationRequest createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainVerificationRequest(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1611862814990L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
new file mode 100644
index 0000000..17593ef
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.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 android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+
+/**
+ * @hide
+ */
+public interface DomainVerificationState {
+
+ /**
+ * @hide
+ */
+ @IntDef({
+ STATE_NO_RESPONSE,
+ STATE_SUCCESS,
+ STATE_MIGRATED,
+ STATE_RESTORED,
+ STATE_APPROVED,
+ STATE_DENIED,
+ STATE_LEGACY_FAILURE,
+ STATE_SYS_CONFIG,
+ STATE_FIRST_VERIFIER_DEFINED
+ })
+ @interface State {
+ }
+
+ // TODO(b/159952358): Document all the places that states need to be updated when one is added
+ /**
+ * @see DomainVerificationManager#STATE_NO_RESPONSE
+ */
+ int STATE_NO_RESPONSE = 0;
+
+ /**
+ * @see DomainVerificationManager#STATE_SUCCESS
+ */
+ int STATE_SUCCESS = 1;
+
+ /**
+ * The system has chosen to ignore the verification agent's opinion on whether the domain should
+ * be verified. This will treat the domain as verified.
+ */
+ int STATE_APPROVED = 2;
+
+ /**
+ * The system has chosen to ignore the verification agent's opinion on whether the domain should
+ * be verified. This will treat the domain as unverified.
+ */
+ int STATE_DENIED = 3;
+
+ /**
+ * The state was migrated from the previous intent filter verification API. This will treat the
+ * domain as verified, but it should be updated by the verification agent. The older API's
+ * collection and handling of verifying domains may lead to improperly migrated state.
+ */
+ int STATE_MIGRATED = 4;
+
+ /**
+ * The state was restored from a user backup or by the system. This is treated as if the domain
+ * was verified, but the verification agent may choose to re-verify this domain to be certain
+ * nothing has changed since the snapshot.
+ */
+ int STATE_RESTORED = 5;
+
+ /**
+ * The domain was failed by a legacy intent filter verification agent from v1 of the API. This
+ * is made distinct from {@link #STATE_FIRST_VERIFIER_DEFINED} to prevent any v2 verification
+ * agent from misinterpreting the result, since {@link #STATE_FIRST_VERIFIER_DEFINED} is agent
+ * specific and can be defined as a special error code.
+ */
+ int STATE_LEGACY_FAILURE = 6;
+
+ /**
+ * The application has been granted auto verification for all domains by configuration on the
+ * system image.
+ *
+ * TODO: Can be stored per-package rather than for all domains for a package to save memory.
+ */
+ int STATE_SYS_CONFIG = 7;
+
+ /**
+ * @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED
+ */
+ int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
+}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
similarity index 87%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
index 284a4ba..ddb5ef8 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.content.pm.verify.domain;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable DomainVerificationUserSelection;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
new file mode 100644
index 0000000..8d16f75
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Contains the user selection state for a package. This means all web HTTP(S) domains
+ * declared by a package in its manifest, whether or not they were marked for auto
+ * verification.
+ * <p>
+ * By default, all apps are allowed to automatically open links with domains that they've
+ * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
+ * The user can decide to disable this, disallowing the application from opening these
+ * links.
+ * <p>
+ * Separately, independent of this toggle, the user can choose specific domains to allow
+ * an app to open, which is reflected as part of {@link #getHostToUserSelectionMap()},
+ * which maps the domain name to the true/false state of whether it was enabled by the user.
+ * <p>
+ * These values can be changed through the
+ * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
+ * boolean)} and
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)} APIs.
+ * <p>
+ * Note that because state is per user, if a different user needs to be changed, one will
+ * need to use {@link Context#createContextAsUser(UserHandle, int)} and hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+ genEqualsHashCode = true)
+public final class DomainVerificationUserSelection implements Parcelable {
+
+ /**
+ * @see DomainVerificationInfo#getIdentifier
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+ private final UUID mIdentifier;
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * The user that this data corresponds to.
+ */
+ @NonNull
+ private final UserHandle mUser;
+
+ /**
+ * Whether or not this package is allowed to open links.
+ */
+ @NonNull
+ private final boolean mLinkHandlingAllowed;
+
+ /**
+ * Retrieve the existing user selection state for the matching
+ * {@link #getPackageName()}, as was previously set by
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)}.
+ *
+ * @return Map of hosts to enabled state for the given package and user.
+ */
+ @NonNull
+ private final Map<String, Boolean> mHostToUserSelectionMap;
+
+
+
+ // 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/pm/domain/verify/DomainVerificationUserSelection.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationUserSelection.
+ *
+ * @param packageName
+ * The package name that this data corresponds to.
+ * @param user
+ * The user that this data corresponds to.
+ * @param linkHandlingAllowed
+ * Whether or not this package is allowed to open links.
+ * @param hostToUserSelectionMap
+ * Retrieve the existing user selection state for the matching
+ * {@link #getPackageName()}, as was previously set by
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)}.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationUserSelection(
+ @NonNull UUID identifier,
+ @NonNull String packageName,
+ @NonNull UserHandle user,
+ @NonNull boolean linkHandlingAllowed,
+ @NonNull Map<String,Boolean> hostToUserSelectionMap) {
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUser = user;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUser);
+ this.mLinkHandlingAllowed = linkHandlingAllowed;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLinkHandlingAllowed);
+ this.mHostToUserSelectionMap = hostToUserSelectionMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToUserSelectionMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * @see DomainVerificationInfo#getIdentifier
+ */
+ @DataClass.Generated.Member
+ public @NonNull UUID getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The user that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Whether or not this package is allowed to open links.
+ */
+ @DataClass.Generated.Member
+ public @NonNull boolean isLinkHandlingAllowed() {
+ return mLinkHandlingAllowed;
+ }
+
+ /**
+ * Retrieve the existing user selection state for the matching
+ * {@link #getPackageName()}, as was previously set by
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)}.
+ *
+ * @return Map of hosts to enabled state for the given package and user.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,Boolean> getHostToUserSelectionMap() {
+ return mHostToUserSelectionMap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationUserSelection { " +
+ "identifier = " + mIdentifier + ", " +
+ "packageName = " + mPackageName + ", " +
+ "user = " + mUser + ", " +
+ "linkHandlingAllowed = " + mLinkHandlingAllowed + ", " +
+ "hostToUserSelectionMap = " + mHostToUserSelectionMap +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationUserSelection that = (DomainVerificationUserSelection) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && java.util.Objects.equals(mUser, that.mUser)
+ && mLinkHandlingAllowed == that.mLinkHandlingAllowed
+ && java.util.Objects.equals(mHostToUserSelectionMap, that.mHostToUserSelectionMap);
+ }
+
+ @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 + java.util.Objects.hashCode(mIdentifier);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mUser);
+ _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostToUserSelectionMap);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<UUID> sParcellingForIdentifier =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForUUID.class);
+ static {
+ if (sParcellingForIdentifier == null) {
+ sParcellingForIdentifier = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForUUID());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mLinkHandlingAllowed) flg |= 0x8;
+ dest.writeByte(flg);
+ sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+ dest.writeString(mPackageName);
+ dest.writeTypedObject(mUser, flags);
+ dest.writeMap(mHostToUserSelectionMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationUserSelection(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean linkHandlingAllowed = (flg & 0x8) != 0;
+ UUID identifier = sParcellingForIdentifier.unparcel(in);
+ String packageName = in.readString();
+ UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+ Map<String,Boolean> hostToUserSelectionMap = new java.util.LinkedHashMap<>();
+ in.readMap(hostToUserSelectionMap, Boolean.class.getClassLoader());
+
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUser = user;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUser);
+ this.mLinkHandlingAllowed = linkHandlingAllowed;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLinkHandlingAllowed);
+ this.mHostToUserSelectionMap = hostToUserSelectionMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToUserSelectionMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationUserSelection> CREATOR
+ = new Parcelable.Creator<DomainVerificationUserSelection>() {
+ @Override
+ public DomainVerificationUserSelection[] newArray(int size) {
+ return new DomainVerificationUserSelection[size];
+ }
+
+ @Override
+ public DomainVerificationUserSelection createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainVerificationUserSelection(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1611799495498L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
new file mode 100644
index 0000000..21dd623b
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import java.util.List;
+
+/**
+ * @see DomainVerificationManager
+ * @hide
+ */
+interface IDomainVerificationManager {
+
+ List<String> getValidVerificationPackageNames();
+
+ @nullable
+ DomainVerificationInfo getDomainVerificationInfo(String packageName);
+
+ @nullable
+ DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName,
+ int userId);
+
+ void setDomainVerificationStatus(String domainSetId, in List<String> domains, int state);
+
+ void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId);
+
+ void setDomainVerificationUserSelection(String domainSetId, in List<String> domains,
+ boolean enabled, int userId);
+}
diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING
new file mode 100644
index 0000000..c6c9791
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "PackageManagerServiceUnitTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.verify.domain"
+ }
+ ]
+ }
+ ]
+}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index f6edb2e..abf694f 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -89,6 +89,11 @@
private static final int NEEDS_COMPAT_RES = 16;
/**
+ * Set if the application needs to be forcibly downscaled
+ */
+ private static final int HAS_OVERRIDE_SCALING = 32;
+
+ /**
* The effective screen density we have selected for this application.
*/
public final int applicationDensity;
@@ -107,6 +112,11 @@
@UnsupportedAppUsage
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
boolean forceCompat) {
+ this(appInfo, screenLayout, sw, forceCompat, 1f);
+ }
+
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+ boolean forceCompat, float overrideScale) {
int compatFlags = 0;
if (appInfo.targetSdkVersion < VERSION_CODES.O) {
@@ -241,7 +251,12 @@
compatFlags |= NEVER_NEEDS_COMPAT;
}
- if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+ if (overrideScale != 1.0f) {
+ applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
+ applicationScale = overrideScale;
+ applicationInvertedScale = 1.0f / overrideScale;
+ compatFlags |= HAS_OVERRIDE_SCALING;
+ } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
applicationScale = 1.0f;
applicationInvertedScale = 1.0f;
@@ -277,7 +292,7 @@
*/
@UnsupportedAppUsage
public boolean isScalingRequired() {
- return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
+ return (mCompatibilityFlags & (SCALING_REQUIRED | HAS_OVERRIDE_SCALING)) != 0;
}
@UnsupportedAppUsage
@@ -303,7 +318,7 @@
*/
@UnsupportedAppUsage
public Translator getTranslator() {
- return isScalingRequired() ? new Translator() : null;
+ return (mCompatibilityFlags & SCALING_REQUIRED) != 0 ? new Translator() : null;
}
/**
@@ -504,6 +519,16 @@
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
+ inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
+ inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
+ inoutConfig.smallestScreenWidthDp =
+ (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
+ inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
+ inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
+ final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
+ if (appBounds != null) {
+ appBounds.scale(invertedRatio);
+ }
}
}
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index f0b218c..abb4f9f 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -261,7 +261,7 @@
@IntRange(from = 0) int baseVersion
) {
try {
- return mIFontManager.updateFont(pfd, signature, baseVersion);
+ return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
} catch (RemoteException e) {
Log.e(TAG, "Failed to call updateFont API", e);
return RESULT_ERROR_REMOTE_EXCEPTION;
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/core/java/android/graphics/fonts/FontUpdateRequest.aidl
similarity index 95%
rename from core/java/android/graphics/fonts/SystemFontState.aidl
rename to core/java/android/graphics/fonts/FontUpdateRequest.aidl
index 19b20f2..f6cb373 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.aidl
@@ -17,4 +17,4 @@
package android.graphics.fonts;
/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+parcelable FontUpdateRequest;
\ No newline at end of file
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
new file mode 100644
index 0000000..db047f8
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/**
+ * 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 Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
+ @Override
+ public FontUpdateRequest createFromParcel(Parcel in) {
+ return new FontUpdateRequest(in);
+ }
+
+ @Override
+ public FontUpdateRequest[] newArray(int size) {
+ return new FontUpdateRequest[size];
+ }
+ };
+
+ @NonNull
+ private final ParcelFileDescriptor mFd;
+ @NonNull
+ private final byte[] mSignature;
+
+ public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
+ mFd = fd;
+ mSignature = signature;
+ }
+
+ private FontUpdateRequest(Parcel in) {
+ mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ mSignature = in.readBlob();
+ }
+
+ @NonNull
+ public ParcelFileDescriptor getFd() {
+ return mFd;
+ }
+
+ @NonNull
+ public byte[] getSignature() {
+ return mSignature;
+ }
+
+ @Override
+ public int describeContents() {
+ return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mFd, flags);
+ dest.writeBlob(mSignature);
+ }
+}
diff --git a/core/java/android/hardware/Battery.java b/core/java/android/hardware/Battery.java
new file mode 100644
index 0000000..24c8d76
--- /dev/null
+++ b/core/java/android/hardware/Battery.java
@@ -0,0 +1,74 @@
+/*
+ * 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.hardware;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.os.BatteryManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The Battery class is a representation of a single battery on a device.
+ */
+public abstract class Battery {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_UNKNOWN,
+ STATUS_CHARGING,
+ STATUS_DISCHARGING,
+ STATUS_NOT_CHARGING,
+ STATUS_FULL
+ })
+ public @interface BatteryStatus {
+ }
+
+ /** Battery status is unknown. */
+ public static final int STATUS_UNKNOWN = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ /** Battery is charging. */
+ public static final int STATUS_CHARGING = BatteryManager.BATTERY_STATUS_CHARGING;
+ /** Battery is discharging. */
+ public static final int STATUS_DISCHARGING = BatteryManager.BATTERY_STATUS_DISCHARGING;
+ /** Battery is connected to power but not charging. */
+ public static final int STATUS_NOT_CHARGING = BatteryManager.BATTERY_STATUS_NOT_CHARGING;
+ /** Battery is full. */
+ public static final int STATUS_FULL = BatteryManager.BATTERY_STATUS_FULL;
+
+ /**
+ * Check whether the hardware has a battery.
+ *
+ * @return True if the hardware has a battery, else false.
+ */
+ public abstract boolean hasBattery();
+
+ /**
+ * Get the battery status.
+ *
+ * @return the battery status.
+ */
+ public abstract @BatteryStatus int getStatus();
+
+ /**
+ * Get remaining battery capacity as float percentage [0.0f, 1.0f] of total capacity
+ * Returns -1 when battery capacity can't be read.
+ *
+ * @return the battery capacity.
+ */
+ public abstract @FloatRange(from = -1.0f, to = 1.0f) float getCapacity();
+}
diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java
index e2d836c..a6c6b46 100644
--- a/core/java/android/hardware/display/BrightnessChangeEvent.java
+++ b/core/java/android/hardware/display/BrightnessChangeEvent.java
@@ -65,6 +65,23 @@
/** If night mode color filter is active this will be the temperature in kelvin */
public final int colorTemperature;
+ /** Whether the bright color reduction color transform is active */
+ public final boolean reduceBrightColors;
+
+ /** How strong the bright color reduction color transform is set (only applicable if active),
+ * specified as an integer from 0 - 100, inclusive. This value (scaled to 0-1, inclusive) is
+ * then used in Ynew = (a * scaledStrength^2 + b * scaledStrength + c) * Ycurrent, where a, b,
+ * and c are coefficients provided in the bright color reduction coefficient matrix, and
+ * Ycurrent is the current hardware brightness in nits.
+ */
+ public final int reduceBrightColorsStrength;
+
+ /** Applied offset for the bright color reduction color transform (only applicable if active).
+ * The offset is computed by summing the coefficients a, b, and c, from the coefficient matrix
+ * and multiplying by the current brightness.
+ */
+ public final float reduceBrightColorsOffset;
+
/** Brightness level before slider adjustment */
public final float lastBrightness;
@@ -105,8 +122,9 @@
private BrightnessChangeEvent(float brightness, long timeStamp, String packageName,
int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel,
float powerBrightnessFactor, boolean nightMode, int colorTemperature,
- float lastBrightness, boolean isDefaultBrightnessConfig, boolean isUserSetBrightness,
- long[] colorValueBuckets, long colorSampleDuration) {
+ boolean reduceBrightColors, int reduceBrightColorsStrength,
+ float reduceBrightColorsOffset, float lastBrightness, boolean isDefaultBrightnessConfig,
+ boolean isUserSetBrightness, long[] colorValueBuckets, long colorSampleDuration) {
this.brightness = brightness;
this.timeStamp = timeStamp;
this.packageName = packageName;
@@ -117,6 +135,9 @@
this.powerBrightnessFactor = powerBrightnessFactor;
this.nightMode = nightMode;
this.colorTemperature = colorTemperature;
+ this.reduceBrightColors = reduceBrightColors;
+ this.reduceBrightColorsStrength = reduceBrightColorsStrength;
+ this.reduceBrightColorsOffset = reduceBrightColorsOffset;
this.lastBrightness = lastBrightness;
this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
this.isUserSetBrightness = isUserSetBrightness;
@@ -136,6 +157,9 @@
this.powerBrightnessFactor = other.powerBrightnessFactor;
this.nightMode = other.nightMode;
this.colorTemperature = other.colorTemperature;
+ this.reduceBrightColors = other.reduceBrightColors;
+ this.reduceBrightColorsStrength = other.reduceBrightColorsStrength;
+ this.reduceBrightColorsOffset = other.reduceBrightColorsOffset;
this.lastBrightness = other.lastBrightness;
this.isDefaultBrightnessConfig = other.isDefaultBrightnessConfig;
this.isUserSetBrightness = other.isUserSetBrightness;
@@ -154,6 +178,9 @@
powerBrightnessFactor = source.readFloat();
nightMode = source.readBoolean();
colorTemperature = source.readInt();
+ reduceBrightColors = source.readBoolean();
+ reduceBrightColorsStrength = source.readInt();
+ reduceBrightColorsOffset = source.readFloat();
lastBrightness = source.readFloat();
isDefaultBrightnessConfig = source.readBoolean();
isUserSetBrightness = source.readBoolean();
@@ -188,6 +215,9 @@
dest.writeFloat(powerBrightnessFactor);
dest.writeBoolean(nightMode);
dest.writeInt(colorTemperature);
+ dest.writeBoolean(reduceBrightColors);
+ dest.writeInt(reduceBrightColorsStrength);
+ dest.writeFloat(reduceBrightColorsOffset);
dest.writeFloat(lastBrightness);
dest.writeBoolean(isDefaultBrightnessConfig);
dest.writeBoolean(isUserSetBrightness);
@@ -207,6 +237,9 @@
private float mPowerBrightnessFactor;
private boolean mNightMode;
private int mColorTemperature;
+ private boolean mReduceBrightColors;
+ private int mReduceBrightColorsStrength;
+ private float mReduceBrightColorsOffset;
private float mLastBrightness;
private boolean mIsDefaultBrightnessConfig;
private boolean mIsUserSetBrightness;
@@ -273,6 +306,24 @@
return this;
}
+ /** {@see BrightnessChangeEvent#reduceBrightColors} */
+ public Builder setReduceBrightColors(boolean reduceBrightColors) {
+ mReduceBrightColors = reduceBrightColors;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#reduceBrightColorsStrength} */
+ public Builder setReduceBrightColorsStrength(int strength) {
+ mReduceBrightColorsStrength = strength;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#reduceBrightColorsOffset} */
+ public Builder setReduceBrightColorsOffset(float offset) {
+ mReduceBrightColorsOffset = offset;
+ return this;
+ }
+
/** {@see BrightnessChangeEvent#lastBrightness} */
public Builder setLastBrightness(float lastBrightness) {
mLastBrightness = lastBrightness;
@@ -304,7 +355,8 @@
public BrightnessChangeEvent build() {
return new BrightnessChangeEvent(mBrightness, mTimeStamp,
mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel,
- mPowerBrightnessFactor, mNightMode, mColorTemperature, mLastBrightness,
+ mPowerBrightnessFactor, mNightMode, mColorTemperature, mReduceBrightColors,
+ mReduceBrightColorsStrength, mReduceBrightColorsOffset, mLastBrightness,
mIsDefaultBrightnessConfig, mIsUserSetBrightness, mColorValueBuckets,
mColorSampleDuration);
}
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index efeb89e..e247df3 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -438,6 +438,56 @@
}
/**
+ * Enables or disables reduce bright colors.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setReduceBrightColorsActivated(boolean activated) {
+ return mManager.setReduceBrightColorsActivated(activated);
+ }
+
+ /**
+ * Returns whether reduce bright colors is currently enabled.
+ *
+ * @hide
+ */
+ public boolean isReduceBrightColorsActivated() {
+ return mManager.isReduceBrightColorsActivated();
+ }
+
+ /**
+ * Set the strength level of bright color reduction to apply to the display.
+ *
+ * @param strength 0-100 (inclusive), where 100 is full strength
+ * @return whether the change was applied successfully
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setReduceBrightColorsStrength(@IntRange(from = 0, to = 100) int strength) {
+ return mManager.setReduceBrightColorsStrength(strength);
+ }
+
+ /**
+ * Gets the strength of the bright color reduction transform.
+ *
+ * @hide
+ */
+ public int getReduceBrightColorsStrength() {
+ return mManager.getReduceBrightColorsStrength();
+ }
+
+ /**
+ * Gets the brightness impact of the bright color reduction transform, as in the factor by which
+ * the current brightness (in nits) should be multiplied to obtain the brightness offset 'b'.
+ *
+ * @hide
+ */
+ public float getReduceBrightColorsOffsetFactor() {
+ return mManager.getReduceBrightColorsOffsetFactor();
+ }
+
+ /**
* Returns {@code true} if Night Display is supported by the device.
*
* @hide
@@ -478,6 +528,15 @@
}
/**
+ * Returns {@code true} if reduce bright colors is supported by the device.
+ *
+ * @hide
+ */
+ public static boolean isReduceBrightColorsAvailable(Context context) {
+ return context.getResources().getBoolean(R.bool.config_reduceBrightColorsAvailable);
+ }
+
+ /**
* Check if the color transforms are color accelerated. Some transforms are experimental only
* on non-accelerated platforms due to the performance implications.
*
@@ -678,6 +737,46 @@
}
}
+ boolean isReduceBrightColorsActivated() {
+ try {
+ return mCdm.isReduceBrightColorsActivated();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean setReduceBrightColorsActivated(boolean activated) {
+ try {
+ return mCdm.setReduceBrightColorsActivated(activated);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ int getReduceBrightColorsStrength() {
+ try {
+ return mCdm.getReduceBrightColorsStrength();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean setReduceBrightColorsStrength(int strength) {
+ try {
+ return mCdm.setReduceBrightColorsStrength(strength);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ float getReduceBrightColorsOffsetFactor() {
+ try {
+ return mCdm.getReduceBrightColorsOffsetFactor();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
int getColorMode() {
try {
return mCdm.getColorMode();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 95f1d12..48b05b7 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -47,20 +47,22 @@
* begins adjusting the power state to match what was requested.
* </p>
*
+ * @param groupId The identifier for the display group being requested to change power state
* @param request The requested power state.
- * @param waitForNegativeProximity If true, issues a request to wait for
+ * @param waitForNegativeProximity If {@code true}, issues a request to wait for
* negative proximity before turning the screen back on, assuming the screen
* was turned off by the proximity sensor.
- * @return True if display is ready, false if there are important changes that must
- * be made asynchronously (such as turning the screen on), in which case the caller
- * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
- * then try the request again later until the state converges.
+ * @return {@code true} if display group is ready, {@code false} if there are important
+ * changes that must be made asynchronously (such as turning the screen on), in which case
+ * the caller should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged}
+ * then try the request again later until the state converges. If the provided {@code groupId}
+ * cannot be found then {@code true} will be returned.
*/
- public abstract boolean requestPowerState(DisplayPowerRequest request,
+ public abstract boolean requestPowerState(int groupId, DisplayPowerRequest request,
boolean waitForNegativeProximity);
/**
- * Returns true if the proximity sensor screen-off function is available.
+ * Returns {@code true} if the proximity sensor screen-off function is available.
*/
public abstract boolean isProximitySensorAvailable();
@@ -71,6 +73,22 @@
public abstract int getDisplayGroupId(int displayId);
/**
+ * Registers a display group listener which will be informed of the addition, removal, or change
+ * of display groups.
+ *
+ * @param listener The listener to register.
+ */
+ public abstract void registerDisplayGroupListener(DisplayGroupListener listener);
+
+ /**
+ * Unregisters a display group listener which will be informed of the addition, removal, or
+ * change of display groups.
+ *
+ * @param listener The listener to unregister.
+ */
+ public abstract void unregisterDisplayGroupListener(DisplayGroupListener listener);
+
+ /**
* Screenshot for internal system-only use such as rotation, etc. This method includes
* secure layers and the result should never be exposed to non-system applications.
* This method does not apply any rotation and provides the output in natural orientation.
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 7b1033d..ef4f5f9 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -45,4 +45,10 @@
boolean isDisplayWhiteBalanceEnabled();
boolean setDisplayWhiteBalanceEnabled(boolean enabled);
+
+ boolean isReduceBrightColorsActivated();
+ boolean setReduceBrightColorsActivated(boolean activated);
+ int getReduceBrightColorsStrength();
+ boolean setReduceBrightColorsStrength(int strength);
+ float getReduceBrightColorsOffsetFactor();
}
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 2650dc5..f097651 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -19,6 +19,8 @@
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
+import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.Parcel;
@@ -81,10 +83,36 @@
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
@FingerprintSensorProperties.SensorType int sensorType,
boolean resetLockoutRequiresHardwareAuthToken) {
- // TODO: Value should be provided from the HAL
+ // TODO(b/179175438): Value should be provided from the HAL
this(sensorId, strength, maxEnrollmentsPerUser, sensorType,
- resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
- 1769 /* sensorLocationY */, 130 /* sensorRadius */);
+ resetLockoutRequiresHardwareAuthToken, 0 /* sensorLocationX */,
+ 0 /* sensorLocationY */, 0 /* sensorRadius */);
+ }
+
+ /**
+ * Initializes SensorProperties with specified values and values obtained from resources using
+ * context.
+ */
+ // TODO(b/179175438): Remove this constructor once all HALs move to AIDL.
+ public FingerprintSensorPropertiesInternal(@NonNull Context context, int sensorId,
+ @SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
+ @FingerprintSensorProperties.SensorType int sensorType,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ super(sensorId, strength, maxEnrollmentsPerUser);
+ this.sensorType = sensorType;
+ this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+
+ int[] props = context.getResources().getIntArray(
+ com.android.internal.R.array.config_udfps_sensor_props);
+ if (props != null && props.length == 3) {
+ this.sensorLocationX = props[0];
+ this.sensorLocationY = props[1];
+ this.sensorRadius = props[2];
+ } else {
+ this.sensorLocationX = 0;
+ this.sensorLocationY = 0;
+ this.sensorRadius = 0;
+ }
}
protected FingerprintSensorPropertiesInternal(Parcel in) {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index b39df4d..c69c47f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -93,6 +93,10 @@
int[] getVibratorIds(int deviceId);
boolean isVibrating(int deviceId);
+ // Input device battery query.
+ int getBatteryStatus(int deviceId);
+ int getBatteryCapacity(int deviceId);
+
void setPointerIconType(int typeId);
void setCustomPointerIcon(in PointerIcon icon);
diff --git a/core/java/android/hardware/input/InputDeviceBattery.java b/core/java/android/hardware/input/InputDeviceBattery.java
new file mode 100644
index 0000000..0fe124e
--- /dev/null
+++ b/core/java/android/hardware/input/InputDeviceBattery.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.hardware.input;
+
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.IInputConstants.INVALID_BATTERY_CAPACITY;
+
+import android.hardware.Battery;
+
+/**
+ * Battery implementation for input devices.
+ *
+ * @hide
+ */
+public final class InputDeviceBattery extends Battery {
+ private static final float NULL_BATTERY_CAPACITY = -1.0f;
+
+ private final InputManager mInputManager;
+ private final int mDeviceId;
+ private final boolean mHasBattery;
+
+ InputDeviceBattery(InputManager inputManager, int deviceId, boolean hasBattery) {
+ mInputManager = inputManager;
+ mDeviceId = deviceId;
+ mHasBattery = hasBattery;
+ }
+
+ @Override
+ public boolean hasBattery() {
+ return mHasBattery;
+ }
+
+ @Override
+ public int getStatus() {
+ if (!mHasBattery) {
+ return BATTERY_STATUS_UNKNOWN;
+ }
+ return mInputManager.getBatteryStatus(mDeviceId);
+ }
+
+ @Override
+ public float getCapacity() {
+ if (mHasBattery) {
+ int capacity = mInputManager.getBatteryCapacity(mDeviceId);
+ if (capacity != INVALID_BATTERY_CAPACITY) {
+ return (float) capacity / 100.0f;
+ }
+ }
+ return NULL_BATTERY_CAPACITY;
+ }
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index e63dc11..185c59d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1245,6 +1245,32 @@
}
/**
+ * Get the battery status of the input device
+ * @param deviceId The input device ID
+ * @hide
+ */
+ public int getBatteryStatus(int deviceId) {
+ try {
+ return mIm.getBatteryStatus(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the remaining battery capacity of the input device
+ * @param deviceId The input device ID
+ * @hide
+ */
+ public int getBatteryCapacity(int deviceId) {
+ try {
+ return mIm.getBatteryCapacity(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Add a runtime association between the input port and the display port. This overrides any
* static associations.
* @param inputPort The port of the input device.
@@ -1471,6 +1497,15 @@
}
/**
+ * Gets a battery object associated with an input device, assuming it has one.
+ * @return The battery, never null.
+ * @hide
+ */
+ public InputDeviceBattery getInputDeviceBattery(int deviceId, boolean hasBattery) {
+ return new InputDeviceBattery(this, deviceId, hasBattery);
+ }
+
+ /**
* Listens for changes in input devices.
*/
public interface InputDeviceListener {
diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS
index e6a04da..d7db7c7 100644
--- a/core/java/android/inputmethodservice/OWNERS
+++ b/core/java/android/inputmethodservice/OWNERS
@@ -2,3 +2,5 @@
set noparent
include /services/core/java/com/android/server/inputmethod/OWNERS
+
+per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index 06d0fc1..5d8122b 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -22,11 +22,12 @@
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.os.Build;
import android.service.NetworkIdentityProto;
import android.telephony.Annotation.NetworkType;
import android.util.proto.ProtoOutputStream;
+import com.android.net.module.util.NetworkIdentityUtils;
+
import java.util.Objects;
/**
@@ -90,7 +91,8 @@
builder.append(mSubType);
}
if (mSubscriberId != null) {
- builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
+ builder.append(", subscriberId=")
+ .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
}
if (mNetworkId != null) {
builder.append(", networkId=").append(mNetworkId);
@@ -111,7 +113,8 @@
// Not dumping mSubType, subtypes are no longer supported.
if (mSubscriberId != null) {
- proto.write(NetworkIdentityProto.SUBSCRIBER_ID, scrubSubscriberId(mSubscriberId));
+ proto.write(NetworkIdentityProto.SUBSCRIBER_ID,
+ NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
}
proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
proto.write(NetworkIdentityProto.ROAMING, mRoaming);
@@ -150,32 +153,6 @@
}
/**
- * Scrub given IMSI on production builds.
- */
- public static String scrubSubscriberId(String subscriberId) {
- if (Build.IS_ENG) {
- return subscriberId;
- } else if (subscriberId != null) {
- // TODO: parse this as MCC+MNC instead of hard-coding
- return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
- } else {
- return "null";
- }
- }
-
- /**
- * Scrub given IMSI on production builds.
- */
- public static String[] scrubSubscriberId(String[] subscriberId) {
- if (subscriberId == null) return null;
- final String[] res = new String[subscriberId.length];
- for (int i = 0; i < res.length; i++) {
- res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
- }
- return res;
- }
-
- /**
* Build a {@link NetworkIdentity} from the given {@link NetworkState} and {@code subType},
* assuming that any mobile networks are using the current IMSI. The subType if applicable,
* should be set as one of the TelephonyManager.NETWORK_TYPE_* constants, or
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index dc33cc7..aa61e03 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -48,6 +48,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.net.module.util.NetworkIdentityUtils;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -296,11 +297,11 @@
builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
if (mSubscriberId != null) {
builder.append(", subscriberId=").append(
- NetworkIdentity.scrubSubscriberId(mSubscriberId));
+ NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
}
if (mMatchSubscriberIds != null) {
builder.append(", matchSubscriberIds=").append(
- Arrays.toString(NetworkIdentity.scrubSubscriberId(mMatchSubscriberIds)));
+ Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds)));
}
if (mNetworkId != null) {
builder.append(", networkId=").append(mNetworkId);
diff --git a/core/java/android/net/UnderlyingNetworkInfo.java b/core/java/android/net/UnderlyingNetworkInfo.java
index 8fb4832..7bf9231 100644
--- a/core/java/android/net/UnderlyingNetworkInfo.java
+++ b/core/java/android/net/UnderlyingNetworkInfo.java
@@ -16,11 +16,15 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -30,6 +34,7 @@
*
* @hide
*/
+@SystemApi(client = MODULE_LIBRARIES)
public final class UnderlyingNetworkInfo implements Parcelable {
/** The owner of this network. */
public final int ownerUid;
@@ -46,7 +51,7 @@
Objects.requireNonNull(underlyingIfaces);
this.ownerUid = ownerUid;
this.iface = iface;
- this.underlyingIfaces = underlyingIfaces;
+ this.underlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces));
}
private UnderlyingNetworkInfo(@NonNull Parcel in) {
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 250cff2..a22d41a 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -26,7 +26,7 @@
import android.widget.TextView;
import com.android.internal.util.HexDump;
-import com.android.org.bouncycastle.asn1.x509.X509Name;
+import com.android.internal.org.bouncycastle.asn1.x509.X509Name;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 33beb6a..fa090f5 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -67,7 +67,6 @@
public class VcnManager {
@NonNull private static final String TAG = VcnManager.class.getSimpleName();
- /** @hide */
@VisibleForTesting
public static final Map<
VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 71d2177..f2b466d 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -70,17 +70,6 @@
public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
/**
- * Modeled power components are used for testing only. They are returned if the
- * {@link BatteryUsageStatsQuery#FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED} is set.
- * The modeled power components are retrieved with {@link #getConsumedPowerForCustomComponent}.
- * The ID of a modeled power component is calculated as
- * (FIRST_MODELED_POWER_COMPONENT_ID + powerComponentId), e.g.
- * FIRST_MODELED_POWER_COMPONENT_ID + POWER_COMPONENT_CPU.
- */
- public static final int FIRST_MODELED_POWER_COMPONENT_ID = 10000;
- public static final int LAST_MODELED_POWER_COMPONENT_ID = 19999;
-
- /**
* Time usage component, describing the particular part of the system
* that was used for the corresponding amount of time.
*
@@ -182,10 +171,9 @@
protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
final PowerComponents.Builder mPowerComponentsBuilder;
- public BaseBuilder(int customPowerComponentCount, int customTimeComponentCount,
- boolean includeModeledComponents) {
+ public BaseBuilder(int customPowerComponentCount, int customTimeComponentCount) {
mPowerComponentsBuilder = new PowerComponents.Builder(customPowerComponentCount,
- customTimeComponentCount, includeModeledComponents);
+ customTimeComponentCount);
}
/**
@@ -216,16 +204,6 @@
}
/**
- * Sets the total amount of power consumed since BatteryStats reset, mAh.
- */
- @SuppressWarnings("unchecked")
- @NonNull
- public T setConsumedPower(double consumedPower) {
- mPowerComponentsBuilder.setTotalPowerConsumed(consumedPower);
- return (T) this;
- }
-
- /**
* Sets the amount of time used by the specified component, e.g. CPU, WiFi etc.
*
* @param componentId The ID of the time component, e.g.
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b846142..cc86a60 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1556,8 +1556,7 @@
public int statSoftIrqTime;
public int statIdlTime;
- // Platform-level low power state stats
- public String statPlatformIdleState;
+ // Low power state stats
public String statSubsystemPowerState;
public HistoryStepDetails() {
@@ -1589,7 +1588,6 @@
out.writeInt(statIrqTime);
out.writeInt(statSoftIrqTime);
out.writeInt(statIdlTime);
- out.writeString(statPlatformIdleState);
out.writeString(statSubsystemPowerState);
}
@@ -1611,7 +1609,6 @@
statIrqTime = in.readInt();
statSoftIrqTime = in.readInt();
statIdlTime = in.readInt();
- statPlatformIdleState = in.readString();
statSubsystemPowerState = in.readString();
}
}
@@ -1656,6 +1653,7 @@
public byte batteryPlugType;
public short batteryTemperature;
+ // Battery voltage in millivolts (mV).
@UnsupportedAppUsage
public char batteryVoltage;
@@ -6599,9 +6597,6 @@
item.append(sb);
item.append(")");
}
- item.append(", PlatformIdleStat ");
- item.append(rec.stepDetails.statPlatformIdleState);
- item.append("\n");
item.append(", SubsystemPowerState ");
item.append(rec.stepDetails.statSubsystemPowerState);
@@ -6639,12 +6634,6 @@
item.append(',');
item.append(rec.stepDetails.statIdlTime);
item.append(',');
- if (rec.stepDetails.statPlatformIdleState != null) {
- item.append(rec.stepDetails.statPlatformIdleState);
- if (rec.stepDetails.statSubsystemPowerState != null) {
- item.append(',');
- }
- }
if (rec.stepDetails.statSubsystemPowerState != null) {
item.append(rec.stepDetails.statSubsystemPowerState);
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index f21a812..04e529e 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -32,6 +32,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
/**
* This class provides an API surface for internal system components to report events that are
@@ -183,8 +184,20 @@
@RequiresPermission(android.Manifest.permission.BATTERY_STATS)
@NonNull
public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
+ return getBatteryUsageStats(List.of(query)).get(0);
+ }
+
+ /**
+ * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+ * and per-UID basis.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+ @NonNull
+ public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
try {
- return mBatteryStats.getBatteryUsageStats(query);
+ return mBatteryStats.getBatteryUsageStats(queries);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index a6df87d..af8e8de 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -114,7 +114,6 @@
public static final class Builder {
private final int mCustomPowerComponentCount;
private final int mCustomTimeComponentCount;
- private final boolean mIncludeModeledComponents;
private double mConsumedPower;
private int mDischargePercentage;
private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders =
@@ -122,11 +121,9 @@
private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders =
new SparseArray<>();
- public Builder(int customPowerComponentCount, int customTimeComponentCount,
- boolean includeModeledComponents) {
+ public Builder(int customPowerComponentCount, int customTimeComponentCount) {
mCustomPowerComponentCount = customPowerComponentCount;
mCustomTimeComponentCount = customTimeComponentCount;
- mIncludeModeledComponents = includeModeledComponents;
}
/**
@@ -169,7 +166,7 @@
UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid);
if (builder == null) {
builder = new UidBatteryConsumer.Builder(mCustomPowerComponentCount,
- mCustomTimeComponentCount, mIncludeModeledComponents, batteryStatsUid);
+ mCustomTimeComponentCount, batteryStatsUid);
mUidBatteryConsumerBuilders.put(uid, builder);
}
return builder;
@@ -185,7 +182,7 @@
SystemBatteryConsumer.Builder builder = mSystemBatteryConsumerBuilders.get(drainType);
if (builder == null) {
builder = new SystemBatteryConsumer.Builder(mCustomPowerComponentCount,
- mCustomTimeComponentCount, mIncludeModeledComponents, drainType);
+ mCustomTimeComponentCount, drainType);
mSystemBatteryConsumerBuilders.put(drainType, builder);
}
return builder;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 9a00679..48e7389 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -38,18 +38,19 @@
* @hide
*/
@IntDef(flag = true, prefix = { "FLAG_BATTERY_USAGE_STATS_" }, value = {
- FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED,
+ FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BatteryUsageStatsFlags {}
/**
- * Indicates that modeled battery usage stats should be returned along with
- * measured ones.
+ * Indicates that power estimations should be based on the usage time and
+ * average power constants provided in the PowerProfile, even if on-device power monitoring
+ * is available.
*
* @hide
*/
- public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED = 1;
+ public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 1;
private final int mFlags;
@@ -112,11 +113,13 @@
}
/**
- * Requests to include modeled battery usage stats along with measured ones.
+ * Requests to return modeled battery usage stats only, even if on-device
+ * power monitoring data is available.
+ *
* Should only be used for testing and debugging.
*/
- public Builder includeModeled() {
- mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED;
+ public Builder powerProfileModeledOnly() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL;
return this;
}
}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 0185ba4..16d041a 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -547,7 +547,8 @@
}
try {
- return transactNative(code, data, reply, flags);
+ boolean replyOwnsNative = (reply == null) ? false : reply.ownsNativeParcelObject();
+ return transactNative(code, data, reply, replyOwnsNative, flags);
} finally {
AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
@@ -572,7 +573,7 @@
* Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
- int flags) throws RemoteException;
+ boolean replyOwnsNativeParcelObject, int flags) throws RemoteException;
/**
* See {@link IBinder#linkToDeath(DeathRecipient, int)}
*/
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index 869a727..cb4e9cb 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -364,8 +364,22 @@
@Override
public long getDuration() {
long maxDuration = Long.MIN_VALUE;
+ boolean hasUnknownStep = false;
for (int i = 0; i < mEffects.size(); i++) {
- maxDuration = Math.max(maxDuration, mEffects.valueAt(i).getDuration());
+ long duration = mEffects.valueAt(i).getDuration();
+ if (duration == Long.MAX_VALUE) {
+ // If any duration is repeating, this combination duration is also repeating.
+ return duration;
+ }
+ maxDuration = Math.max(maxDuration, duration);
+ // If any step is unknown, this combination duration will also be unknown, unless
+ // any step is repeating. Repeating vibrations take precedence over non-repeating
+ // ones in the service, so continue looping to check for repeating steps.
+ hasUnknownStep |= duration < 0;
+ }
+ if (hasUnknownStep) {
+ // If any step is unknown, this combination duration is also unknown.
+ return -1;
}
return maxDuration;
}
@@ -477,16 +491,25 @@
@Override
public long getDuration() {
+ boolean hasUnknownStep = false;
long durations = 0;
final int effectCount = mEffects.size();
for (int i = 0; i < effectCount; i++) {
CombinedVibrationEffect effect = mEffects.get(i);
long duration = effect.getDuration();
- if (duration < 0) {
- // If any duration is unknown, this combination duration is also unknown.
+ if (duration == Long.MAX_VALUE) {
+ // If any duration is repeating, this combination duration is also repeating.
return duration;
}
durations += duration;
+ // If any step is unknown, this combination duration will also be unknown, unless
+ // any step is repeating. Repeating vibrations take precedence over non-repeating
+ // ones in the service, so continue looping to check for repeating steps.
+ hasUnknownStep |= duration < 0;
+ }
+ if (hasUnknownStep) {
+ // If any step is unknown, this combination duration is also unknown.
+ return -1;
}
long delays = 0;
for (int i = 0; i < effectCount; i++) {
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 804dc10..f9e2947 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.CombinedVibrationEffect;
+import android.os.IVibratorStateListener;
import android.os.VibrationAttributes;
import android.os.VibratorInfo;
@@ -24,6 +25,9 @@
interface IVibratorManagerService {
int[] getVibratorIds();
VibratorInfo getVibratorInfo(int vibratorId);
+ boolean isVibrating(int vibratorId);
+ boolean registerVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
+ boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
in CombinedVibrationEffect effect, in VibrationAttributes attributes);
void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect,
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index fc65090..5f5a910 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3729,4 +3729,9 @@
public long getBlobAshmemSize() {
return nativeGetBlobAshmemSize(mNativePtr);
}
+
+ /** @hide */
+ /*package*/ boolean ownsNativeParcelObject() {
+ return mOwnsNativeParcelObject;
+ }
}
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 18dca68..1337d55 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -33,22 +33,21 @@
private final double[] mPowerComponents;
private final long[] mTimeComponents;
private final int mCustomPowerComponentCount;
- private final int mModeledPowerComponentOffset;
PowerComponents(@NonNull Builder builder) {
- mTotalPowerConsumed = builder.mTotalPowerConsumed;
mCustomPowerComponentCount = builder.mCustomPowerComponentCount;
- mModeledPowerComponentOffset = builder.mModeledPowerComponentOffset;
mPowerComponents = builder.mPowerComponents;
mTimeComponents = builder.mTimeComponents;
+ double totalPower = 0;
+ for (int i = mPowerComponents.length - 1; i >= 0; i--) {
+ totalPower += mPowerComponents[i];
+ }
+ mTotalPowerConsumed = totalPower;
}
PowerComponents(@NonNull Parcel source) {
mTotalPowerConsumed = source.readDouble();
mCustomPowerComponentCount = source.readInt();
- mModeledPowerComponentOffset =
- BatteryConsumer.POWER_COMPONENT_COUNT + mCustomPowerComponentCount
- - BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID;
mPowerComponents = source.createDoubleArray();
mTimeComponents = source.createLongArray();
}
@@ -102,14 +101,6 @@
throw new IllegalArgumentException(
"Unsupported custom power component ID: " + componentId);
}
- } else if (componentId >= BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- && componentId < BatteryConsumer.LAST_MODELED_POWER_COMPONENT_ID) {
- try {
- return mPowerComponents[mModeledPowerComponentOffset + componentId];
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new IllegalArgumentException(
- "Unsupported modeled power component ID: " + componentId);
- }
} else {
throw new IllegalArgumentException(
"Unsupported custom power component ID: " + componentId);
@@ -158,38 +149,20 @@
* Builder for PowerComponents.
*/
static final class Builder {
- private double mTotalPowerConsumed;
private final double[] mPowerComponents;
private final int mCustomPowerComponentCount;
private final long[] mTimeComponents;
- private final int mModeledPowerComponentOffset;
- Builder(int customPowerComponentCount, int customTimeComponentCount,
- boolean includeModeledPowerComponents) {
+ Builder(int customPowerComponentCount, int customTimeComponentCount) {
mCustomPowerComponentCount = customPowerComponentCount;
int powerComponentCount =
BatteryConsumer.POWER_COMPONENT_COUNT + customPowerComponentCount;
- if (includeModeledPowerComponents) {
- powerComponentCount += BatteryConsumer.POWER_COMPONENT_COUNT;
- }
mPowerComponents = new double[powerComponentCount];
- mModeledPowerComponentOffset =
- BatteryConsumer.POWER_COMPONENT_COUNT + mCustomPowerComponentCount
- - BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID;
mTimeComponents =
new long[BatteryConsumer.TIME_COMPONENT_COUNT + customTimeComponentCount];
}
/**
- * Sets the sum amount of power consumed since BatteryStats reset.
- */
- @NonNull
- public Builder setTotalPowerConsumed(double totalPowerConsumed) {
- mTotalPowerConsumed = totalPowerConsumed;
- return this;
- }
-
- /**
* Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
*
* @param componentId The ID of the power component, e.g.
@@ -228,14 +201,6 @@
throw new IllegalArgumentException(
"Unsupported custom power component ID: " + componentId);
}
- } else if (componentId >= BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- && componentId < BatteryConsumer.LAST_MODELED_POWER_COMPONENT_ID) {
- try {
- mPowerComponents[mModeledPowerComponentOffset + componentId] = componentPower;
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new IllegalArgumentException(
- "Unsupported modeled power component ID: " + componentId);
- }
} else {
throw new IllegalArgumentException(
"Unsupported custom power component ID: " + componentId);
diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java
index 49bf084..fc4aa93 100644
--- a/core/java/android/os/SystemBatteryConsumer.java
+++ b/core/java/android/os/SystemBatteryConsumer.java
@@ -123,8 +123,8 @@
private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
Builder(int customPowerComponentCount, int customTimeComponentCount,
- boolean includeModeledComponents, @DrainType int drainType) {
- super(customPowerComponentCount, customTimeComponentCount, includeModeledComponents);
+ @DrainType int drainType) {
+ super(customPowerComponentCount, customTimeComponentCount);
mDrainType = drainType;
}
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 3161766..3ffaa9e 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -98,8 +98,8 @@
private boolean mSystemComponent;
public Builder(int customPowerComponentCount, int customTimeComponentCount,
- boolean includeModeledComponents, BatteryStats.Uid batteryStatsUid) {
- super(customPowerComponentCount, customTimeComponentCount, includeModeledComponents);
+ BatteryStats.Uid batteryStatsUid) {
+ super(customPowerComponentCount, customTimeComponentCount);
mBatteryStatsUid = batteryStatsUid;
mUid = batteryStatsUid.getUid();
}
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 2093077..217f178 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -268,7 +268,7 @@
}
/** @hide */
- public String usageToString(int usage) {
+ public static String usageToString(int usage) {
switch (usage) {
case USAGE_UNKNOWN:
return "UNKNOWN";
diff --git a/core/java/android/os/strictmode/IncorrectContextUseViolation.java b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
index 647db17..11d26ca 100644
--- a/core/java/android/os/strictmode/IncorrectContextUseViolation.java
+++ b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
@@ -16,19 +16,20 @@
package android.os.strictmode;
+import android.annotation.NonNull;
import android.content.Context;
/**
- * Incorrect usage of {@link Context}, such as obtaining a visual service from non-visual
- * {@link Context} instance.
+ * Incorrect usage of {@link Context}, such as obtaining a UI service from non-UI {@link Context}
+ * instance.
+ *
* @see Context#getSystemService(String)
- * @see Context#getDisplayNoVerify()
- * @hide
+ * @see Context#isUiContext(Context)
+ * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
*/
public final class IncorrectContextUseViolation extends Violation {
- /** @hide */
- public IncorrectContextUseViolation(String message, Throwable originStack) {
+ public IncorrectContextUseViolation(@NonNull String message, @NonNull Throwable originStack) {
super(message);
initCause(originStack);
}
diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md
index 4224b7a..dfe748b 100644
--- a/core/java/android/permission/Permissions.md
+++ b/core/java/android/permission/Permissions.md
@@ -809,6 +809,9 @@
permissions. It is unlikely that 3rd party apps will be able to use APIs protected by signature
permissions as they are usually not signed with the platform certificate.
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
Such permissions are defined and checked like an install time permission.
### Preinstalled permissions
@@ -819,6 +822,9 @@
Hence this permission level is discouraged unless there are
[further restrictions](#restricted-by-tests).
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
Such permissions are defined and checked like an install time permission.
### Privileged permissions
@@ -833,6 +839,9 @@
Hence this permission level is discouraged unless there are
[further restrictions](#restricted-by-tests).
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
Such permissions are defined and checked like an install time permission.
#### Restricted by tests
@@ -890,8 +899,16 @@
Which apps qualify for such a permission level is flexible and custom for each such level. Usually
they refer to a single or small set of apps, usually - but not always - apps defined in AOSP.
+This type of permission is deprecated in favor of
+[role protected permissions](#role-protected-permissions).
+
These permissions are defined and checked like an install time permission.
+### Role protected permissions
+
+See
+[Using role for permission protection](../../../../../../packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md).
+
### Development permissions
> Not recommended
diff --git a/core/java/android/provider/OWNERS b/core/java/android/provider/OWNERS
index cb1509a..cb06515 100644
--- a/core/java/android/provider/OWNERS
+++ b/core/java/android/provider/OWNERS
@@ -1,5 +1,6 @@
per-file *BlockedNumber* = file:/telephony/OWNERS
per-file *Telephony* = file:/telephony/OWNERS
+per-file *SimPhonebook* = file:/telephony/OWNERS
per-file *CallLog* = file:platform/packages/providers/ContactsProvider:/OWNERS
per-file *Contacts* = file:platform/packages/providers/ContactsProvider:/OWNERS
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6dab4c0..9603f4d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5931,6 +5931,13 @@
public static final String ADAPTIVE_SLEEP = "adaptive_sleep";
/**
+ * Setting key to indicate whether camera-based autorotate is enabled.
+ *
+ * @hide
+ */
+ public static final String CAMERA_AUTOROTATE = "camera_autorotate";
+
+ /**
* @deprecated Use {@link android.provider.Settings.Global#DEVELOPMENT_SETTINGS_ENABLED}
* instead
*/
@@ -13393,6 +13400,24 @@
"euicc_factory_reset_timeout_millis";
/**
+ * Flag to set the waiting time for euicc slot switch.
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String EUICC_SWITCH_SLOT_TIMEOUT_MILLIS =
+ "euicc_switch_slot_timeout_millis";
+
+ /**
+ * Flag to set the waiting time for enabling multi SIM slot.
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String ENABLE_MULTI_SLOT_TIMEOUT_MILLIS =
+ "enable_multi_slot_timeout_millis";
+
+ /**
* Flag to set the timeout for when to refresh the storage settings cached data.
* Type: long
*
@@ -13555,6 +13580,28 @@
public static final String POWER_BUTTON_VERY_LONG_PRESS =
"power_button_very_long_press";
+
+ /**
+ * Keyguard should be on the left hand side of the screen, for wide screen layouts.
+ *
+ * @hide
+ */
+ public static final int ONE_HANDED_KEYGUARD_SIDE_LEFT = 0;
+
+ /**
+ * Keyguard should be on the right hand side of the screen, for wide screen layouts.
+ *
+ * @hide
+ */
+ public static final int ONE_HANDED_KEYGUARD_SIDE_RIGHT = 1;
+ /**
+ * In one handed mode, which side the keyguard should be on. Allowable values are one of
+ * the ONE_HANDED_KEYGUARD_SIDE_* constants.
+ *
+ * @hide
+ */
+ public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side";
+
/**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
new file mode 100644
index 0000000..2efc212
--- /dev/null
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN_PATH_SEGMENT;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN_PATH_SEGMENT;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN_PATH_SEGMENT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The contract between the provider of contact records on the device's SIM cards and applications.
+ * Contains definitions of the supported URIs and columns.
+ *
+ * <p>This content provider does not support any of the QUERY_ARG_SQL* bundle arguments. An
+ * IllegalArgumentException will be thrown if these are included.
+ */
+public final class SimPhonebookContract {
+
+ /** The authority for the SIM phonebook provider. */
+ public static final String AUTHORITY = "com.android.simphonebook";
+ /** The content:// style uri to the authority for the SIM phonebook provider. */
+ @NonNull
+ public static final Uri AUTHORITY_URI = Uri.parse("content://com.android.simphonebook");
+ /**
+ * The Uri path element used to indicate that the following path segment is a subscription ID
+ * for the SIM card that will be operated on.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
+
+ private SimPhonebookContract() {
+ }
+
+ /**
+ * Returns the Uri path segment used to reference the specified elementary file type for Uris
+ * returned by this API.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public static String getEfUriPath(@ElementaryFiles.EfType int efType) {
+ switch (efType) {
+ case EF_ADN:
+ return EF_ADN_PATH_SEGMENT;
+ case EF_FDN:
+ return EF_FDN_PATH_SEGMENT;
+ case EF_SDN:
+ return EF_SDN_PATH_SEGMENT;
+ default:
+ throw new IllegalArgumentException("Unsupported EfType " + efType);
+ }
+ }
+
+ /** Constants for the contact records on a SIM card. */
+ public static final class SimRecords {
+
+ /**
+ * The subscription ID of the SIM the record is from.
+ *
+ * @see SubscriptionInfo#getSubscriptionId()
+ */
+ public static final String SUBSCRIPTION_ID = "subscription_id";
+ /**
+ * The type of the elementary file the record is from.
+ *
+ * @see ElementaryFiles#EF_ADN
+ * @see ElementaryFiles#EF_FDN
+ * @see ElementaryFiles#EF_SDN
+ */
+ public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+ /**
+ * The 1-based offset of the record in the elementary file that contains it.
+ *
+ * <p>This can be used to access individual SIM records by appending it to the
+ * elementary file URIs but it is not like a normal database ID because it is not
+ * auto-incrementing and it is not unique across SIM cards or elementary files. Hence, care
+ * should be taken when using it to ensure that it is applied to the correct SIM and EF.
+ *
+ * @see #getItemUri(int, int, int)
+ */
+ public static final String RECORD_NUMBER = "record_number";
+ /**
+ * The name for this record.
+ *
+ * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
+ * exceeds the maximum supported length or contains unsupported characters.
+ * {@link #validateName(ContentResolver, int, int, String)} )} can be used to
+ * check whether the name is supported.
+ *
+ * @see ElementaryFiles#NAME_MAX_LENGTH
+ * @see #validateName(ContentResolver, int, int, String) )
+ */
+ public static final String NAME = "name";
+ /**
+ * The phone number for this record.
+ *
+ * <p>Only dialable characters are supported.
+ *
+ * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
+ * exceeds the maximum supported length or contains unsupported characters.
+ *
+ * @see ElementaryFiles#PHONE_NUMBER_MAX_LENGTH
+ * @see android.telephony.PhoneNumberUtils#isDialable(char)
+ */
+ public static final String PHONE_NUMBER = "phone_number";
+
+ /** The MIME type of a CONTENT_URI subdirectory of a single SIM record. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
+ /** The MIME type of CONTENT_URI providing a directory of SIM records. */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
+
+ /**
+ * The path segment that is appended to {@link #getContentUri(int, int)} which indicates
+ * that the following path segment contains a name to be validated.
+ *
+ * @hide
+ * @see #validateName(ContentResolver, int, int, String)
+ */
+ @SystemApi
+ public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+
+ /**
+ * The key for a cursor extra that contains the result of a validate name query.
+ *
+ * @hide
+ * @see #validateName(ContentResolver, int, int, String)
+ */
+ @SystemApi
+ public static final String EXTRA_NAME_VALIDATION_RESULT =
+ "android.provider.extra.NAME_VALIDATION_RESULT";
+
+
+ /**
+ * Key for the PIN2 needed to modify FDN record that should be passed in the Bundle
+ * passed to {@link ContentResolver#insert(Uri, ContentValues, Bundle)},
+ * {@link ContentResolver#update(Uri, ContentValues, Bundle)}
+ * and {@link ContentResolver#delete(Uri, Bundle)}.
+ *
+ * <p>Modifying FDN records also requires either
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
+ * {@link TelephonyManager#hasCarrierPrivileges()}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
+
+ private SimRecords() {
+ }
+
+ /**
+ * Returns the content Uri for the specified elementary file on the specified SIM.
+ *
+ * <p>When queried this Uri will return all of the contact records in the specified
+ * elementary file on the specified SIM. The available subscriptionIds and efTypes can
+ * be discovered by querying {@link ElementaryFiles#CONTENT_URI}.
+ *
+ * <p>If a SIM with the provided subscription ID does not exist or the SIM with the provided
+ * subscription ID doesn't support the specified entity file then queries will return
+ * and empty cursor and inserts will throw an {@link IllegalArgumentException}
+ *
+ * @param subscriptionId the subscriptionId of the SIM card that this Uri will reference
+ * @param efType the elementary file on the SIM that this Uri will reference
+ * @see ElementaryFiles#EF_ADN
+ * @see ElementaryFiles#EF_FDN
+ * @see ElementaryFiles#EF_SDN
+ */
+ @NonNull
+ public static Uri getContentUri(int subscriptionId, @ElementaryFiles.EfType int efType) {
+ return buildContentUri(subscriptionId, efType).build();
+ }
+
+ /**
+ * Content Uri for the specific SIM record with the provided {@link #RECORD_NUMBER}.
+ *
+ * <p>When queried this will return the record identified by the provided arguments.
+ *
+ * <p>For a non-existent record:
+ * <ul>
+ * <li>query will return an empty cursor</li>
+ * <li>update will return 0</li>
+ * <li>delete will return 0</li>
+ * </ul>
+ *
+ * @param subscriptionId the subscription ID of the SIM containing the record. If no SIM
+ * with this subscription ID exists then it will be treated as a
+ * non-existent record
+ * @param efType the elementary file type containing the record. If the specified
+ * SIM doesn't support this elementary file then it will be treated
+ * as a non-existent record.
+ * @param recordNumber the record number of the record this Uri should reference. This
+ * must be greater than 0. If there is no record with this record
+ * number in the specified entity file then it will be treated as a
+ * non-existent record.
+ */
+ @NonNull
+ public static Uri getItemUri(
+ int subscriptionId, @ElementaryFiles.EfType int efType, int recordNumber) {
+ // Elementary file record indices are 1-based.
+ Preconditions.checkArgument(recordNumber > 0, "Invalid recordNumber");
+
+ return buildContentUri(subscriptionId, efType)
+ .appendPath(String.valueOf(recordNumber))
+ .build();
+ }
+
+ /**
+ * Validates a value that is being provided for the {@link #NAME} column.
+ *
+ * <p>The return value can be used to check if the name is valid. If it is not valid then
+ * inserts and updates to the specified elementary file that use the provided name value
+ * will throw an {@link IllegalArgumentException}.
+ *
+ * <p>If the specified SIM or elementary file don't exist then
+ * {@link NameValidationResult#getMaxEncodedLength()} will be zero and
+ * {@link NameValidationResult#isValid()} will return false.
+ */
+ @NonNull
+ @WorkerThread
+ public static NameValidationResult validateName(
+ @NonNull ContentResolver resolver, int subscriptionId,
+ @ElementaryFiles.EfType int efType,
+ @NonNull String name) {
+ Bundle queryArgs = new Bundle();
+ queryArgs.putString(SimRecords.NAME, name);
+ try (Cursor cursor =
+ resolver.query(buildContentUri(subscriptionId, efType)
+ .appendPath(VALIDATE_NAME_PATH_SEGMENT)
+ .build(), null, queryArgs, null)) {
+ NameValidationResult result = cursor.getExtras()
+ .getParcelable(EXTRA_NAME_VALIDATION_RESULT);
+ return result != null ? result : new NameValidationResult(name, "", 0, 0);
+ }
+ }
+
+ private static Uri.Builder buildContentUri(
+ int subscriptionId, @ElementaryFiles.EfType int efType) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(AUTHORITY)
+ .appendPath(SUBSCRIPTION_ID_PATH_SEGMENT)
+ .appendPath(String.valueOf(subscriptionId))
+ .appendPath(getEfUriPath(efType));
+ }
+
+ /** Contains details about the validity of a value provided for the {@link #NAME} column. */
+ public static final class NameValidationResult implements Parcelable {
+
+ @NonNull
+ public static final Creator<NameValidationResult> CREATOR =
+ new Creator<NameValidationResult>() {
+
+ @Override
+ public NameValidationResult createFromParcel(@NonNull Parcel in) {
+ return new NameValidationResult(in);
+ }
+
+ @NonNull
+ @Override
+ public NameValidationResult[] newArray(int size) {
+ return new NameValidationResult[size];
+ }
+ };
+
+ private final String mName;
+ private final String mSanitizedName;
+ private final int mEncodedLength;
+ private final int mMaxEncodedLength;
+
+ /** Creates a new instance from the provided values. */
+ public NameValidationResult(@NonNull String name, @NonNull String sanitizedName,
+ int encodedLength, int maxEncodedLength) {
+ this.mName = Objects.requireNonNull(name);
+ this.mSanitizedName = Objects.requireNonNull(sanitizedName);
+ this.mEncodedLength = encodedLength;
+ this.mMaxEncodedLength = maxEncodedLength;
+ }
+
+ private NameValidationResult(Parcel in) {
+ this(in.readString(), in.readString(), in.readInt(), in.readInt());
+ }
+
+ /** Returns the original name that is being validated. */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns a sanitized copy of the original name with all unsupported characters
+ * replaced with spaces.
+ */
+ @NonNull
+ public String getSanitizedName() {
+ return mSanitizedName;
+ }
+
+ /**
+ * Returns whether the original name isValid.
+ *
+ * <p>If this returns false then inserts and updates using the name will throw an
+ * {@link IllegalArgumentException}
+ */
+ public boolean isValid() {
+ return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength
+ && Objects.equals(
+ mName, mSanitizedName);
+ }
+
+ /** Returns whether the character at the specified position is supported by the SIM. */
+ public boolean isSupportedCharacter(int position) {
+ return mName.charAt(position) == mSanitizedName.charAt(position);
+ }
+
+ /**
+ * Returns the number of bytes required to save the name.
+ *
+ * <p>This may be more than the number of characters in the name.
+ */
+ public int getEncodedLength() {
+ return mEncodedLength;
+ }
+
+ /**
+ * Returns the maximum number of bytes that are supported for the name.
+ *
+ * @see ElementaryFiles#NAME_MAX_LENGTH
+ */
+ public int getMaxEncodedLength() {
+ return mMaxEncodedLength;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeString(mSanitizedName);
+ dest.writeInt(mEncodedLength);
+ dest.writeInt(mMaxEncodedLength);
+ }
+ }
+ }
+
+ /** Constants for metadata about the elementary files of the SIM cards in the phone. */
+ public static final class ElementaryFiles {
+
+ /** {@link SubscriptionInfo#getSimSlotIndex()} of the SIM for this row. */
+ public static final String SLOT_INDEX = "slot_index";
+ /** {@link SubscriptionInfo#getSubscriptionId()} of the SIM for this row. */
+ public static final String SUBSCRIPTION_ID = "subscription_id";
+ /**
+ * The elementary file type for this row.
+ *
+ * @see ElementaryFiles#EF_ADN
+ * @see ElementaryFiles#EF_FDN
+ * @see ElementaryFiles#EF_SDN
+ */
+ public static final String EF_TYPE = "ef_type";
+ /** The maximum number of records supported by the elementary file. */
+ public static final String MAX_RECORDS = "max_records";
+ /** Count of the number of records that are currently stored in the elementary file. */
+ public static final String RECORD_COUNT = "record_count";
+ /** The maximum length supported for the name of a record in the elementary file. */
+ public static final String NAME_MAX_LENGTH = "name_max_length";
+ /**
+ * The maximum length supported for the phone number of a record in the elementary file.
+ */
+ public static final String PHONE_NUMBER_MAX_LENGTH = "phone_number_max_length";
+
+ /**
+ * A value for an elementary file that is not recognized.
+ *
+ * <p>Generally this should be ignored. If new values are added then this will be used
+ * for apps that target SDKs where they aren't defined.
+ */
+ public static final int EF_UNKNOWN = 0;
+ /**
+ * Type for accessing records in the "abbreviated dialing number" (ADN) elementary file on
+ * the SIM.
+ *
+ * <p>ADN records are typically user created.
+ */
+ public static final int EF_ADN = 1;
+ /**
+ * Type for accessing records in the "fixed dialing number" (FDN) elementary file on the
+ * SIM.
+ *
+ * <p>FDN numbers are the numbers that are allowed to dialed for outbound calls when FDN is
+ * enabled.
+ *
+ * <p>FDN records cannot be modified by applications. Hence, insert, update and
+ * delete methods operating on this Uri will throw UnsupportedOperationException
+ */
+ public static final int EF_FDN = 2;
+ /**
+ * Type for accessing records in the "service dialing number" (SDN) elementary file on the
+ * SIM.
+ *
+ * <p>Typically SDNs are preset numbers provided by the carrier for common operations (e.g.
+ * voicemail, check balance, etc).
+ *
+ * <p>SDN records cannot be modified by applications. Hence, insert, update and delete
+ * methods operating on this Uri will throw UnsupportedOperationException
+ */
+ public static final int EF_SDN = 3;
+ /** @hide */
+ @SystemApi
+ public static final String EF_ADN_PATH_SEGMENT = "adn";
+ /** @hide */
+ @SystemApi
+ public static final String EF_FDN_PATH_SEGMENT = "fdn";
+ /** @hide */
+ @SystemApi
+ public static final String EF_SDN_PATH_SEGMENT = "sdn";
+ /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
+ /** The MIME type of a CONTENT_URI subdirectory of a single ADN-like elementary file. */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/sim-elementary-file";
+ /**
+ * The Uri path segment used to construct Uris for the metadata defined in this class.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
+
+ /** Content URI for the ADN-like elementary files available on the device. */
+ @NonNull
+ public static final Uri CONTENT_URI = AUTHORITY_URI
+ .buildUpon()
+ .appendPath(ELEMENTARY_FILES_PATH_SEGMENT).build();
+
+ private ElementaryFiles() {
+ }
+
+ /**
+ * Returns a content uri for a specific elementary file.
+ *
+ * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown.
+ * If the SIM doesn't support the specified elementary file it will have a zero value for
+ * {@link #MAX_RECORDS}.
+ */
+ @NonNull
+ public static Uri getItemUri(int subscriptionId, @EfType int efType) {
+ return CONTENT_URI.buildUpon().appendPath(SUBSCRIPTION_ID_PATH_SEGMENT)
+ .appendPath(String.valueOf(subscriptionId))
+ .appendPath(getEfUriPath(efType))
+ .build();
+ }
+
+ /**
+ * Annotation for the valid elementary file types.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"EF"},
+ value = {EF_UNKNOWN, EF_ADN, EF_FDN, EF_SDN})
+ public @interface EfType {
+ }
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index f994d29..c39b8c5 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -80,6 +80,7 @@
public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS =
Tag.MIN_SECONDS_BETWEEN_OPS; // KM_UINT | 403;
public static final int KM_TAG_MAX_USES_PER_BOOT = Tag.MAX_USES_PER_BOOT; // KM_UINT | 404;
+ public static final int KM_TAG_USAGE_COUNT_LIMIT = Tag.USAGE_COUNT_LIMIT; // KM_UINT | 405;
public static final int KM_TAG_USER_ID = Tag.USER_ID; // KM_UINT | 501;
public static final int KM_TAG_USER_SECURE_ID = Tag.USER_SECURE_ID; // KM_ULONG_REP | 502;
diff --git a/core/java/android/service/attestation/ImpressionAttestationService.java b/core/java/android/service/attestation/ImpressionAttestationService.java
deleted file mode 100644
index 968d533..0000000
--- a/core/java/android/service/attestation/ImpressionAttestationService.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.attestation;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteCallback;
-
-/**
- * A service that handles generating and verify ImpressionTokens.
- *
- * The service will generate an ImpressionToken based on arguments passed in. Then later that same
- * ImpressionToken can be verified to determine that it was created by the system.
- *
- * @hide
- */
-@SystemApi
-public abstract class ImpressionAttestationService extends Service {
- /** @hide **/
- public static final String EXTRA_IMPRESSION_TOKEN =
- "android.service.attestation.extra.IMPRESSION_TOKEN";
-
- /** @hide **/
- public static final String EXTRA_VERIFICATION_STATUS =
- "android.service.attestation.extra.VERIFICATION_STATUS";
-
- /**
- * Manifest metadata key for the resource string array containing the names of all impression
- * attestation algorithms provided by the service.
- *
- * @hide
- */
- public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
- "android.attestation.available_algorithms";
-
- /**
- * The {@link Intent} action that must be declared as handled by a service in its manifest
- * for the system to recognize it as an impression attestation providing service.
- *
- * @hide
- */
- public static final String SERVICE_INTERFACE =
- "android.service.attestation.ImpressionAttestationService";
-
- private ImpressionAttestationServiceWrapper mWrapper;
- private Handler mHandler;
-
- public ImpressionAttestationService() {
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- mWrapper = new ImpressionAttestationServiceWrapper();
- mHandler = new Handler(Looper.getMainLooper(), null, true);
- }
-
- @NonNull
- @Override
- public final IBinder onBind(@NonNull Intent intent) {
- return mWrapper;
- }
-
- /**
- * Generates the impression token that can be used to validate that the system
- * generated the token.
- *
- * @param salt The salt to use when generating the hmac. This should be unique to the
- * caller so the token cannot be verified by any other process.
- * @param screenshot The screenshot buffer for the content to attest.
- * @param bounds The size and position of the content being attested in the window.
- * @param hashAlgorithm The String for the hashing algorithm to use based values in
- * {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
- * @return An impression token that can be used to validate information about the content.
- * Returns null when the arguments sent are invalid.
- */
- @Nullable
- public abstract ImpressionToken onGenerateImpressionToken(@NonNull byte[] salt,
- @NonNull HardwareBuffer screenshot, @NonNull Rect bounds,
- @NonNull String hashAlgorithm);
-
- /**
- * Call to verify that the impressionToken passed in was generated by the system.
- *
- * @param salt The salt value to use when verifying the hmac. This should be the
- * same value that was passed to
- * {@link #onGenerateImpressionToken(byte[],
- * HardwareBuffer, Rect, String)} to
- * generate the token.
- * @param impressionToken The token to verify that it was generated by the system.
- * @return true if the token can be verified that it was generated by the system.
- */
- public abstract boolean onVerifyImpressionToken(@NonNull byte[] salt,
- @NonNull ImpressionToken impressionToken);
-
- private void generateImpressionToken(byte[] salt, HardwareBuffer screenshot, Rect bounds,
- String hashAlgorithm, RemoteCallback callback) {
- ImpressionToken impressionToken = onGenerateImpressionToken(salt, screenshot, bounds,
- hashAlgorithm);
- final Bundle data = new Bundle();
- data.putParcelable(EXTRA_IMPRESSION_TOKEN, impressionToken);
- callback.sendResult(data);
- }
-
- private void verifyImpressionToken(byte[] salt, ImpressionToken impressionToken,
- RemoteCallback callback) {
- boolean verificationStatus = onVerifyImpressionToken(salt, impressionToken);
- final Bundle data = new Bundle();
- data.putBoolean(EXTRA_VERIFICATION_STATUS, verificationStatus);
- callback.sendResult(data);
- }
-
- private final class ImpressionAttestationServiceWrapper extends
- IImpressionAttestationService.Stub {
- @Override
- public void generateImpressionToken(byte[] salt, HardwareBuffer screenshot, Rect bounds,
- String hashAlgorithm, RemoteCallback callback) {
- mHandler.sendMessage(
- obtainMessage(ImpressionAttestationService::generateImpressionToken,
- ImpressionAttestationService.this, salt, screenshot, bounds,
- hashAlgorithm, callback));
- }
-
- @Override
- public void verifyImpressionToken(byte[] salt, ImpressionToken impressionToken,
- RemoteCallback callback) {
- mHandler.sendMessage(obtainMessage(ImpressionAttestationService::verifyImpressionToken,
- ImpressionAttestationService.this, salt, impressionToken, callback));
- }
- }
-}
diff --git a/core/java/android/service/attestation/OWNERS b/core/java/android/service/attestation/OWNERS
deleted file mode 100644
index b9e7b99..0000000
--- a/core/java/android/service/attestation/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-chaviw@google.com
-ogunwale@google.com
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 4679c56..e3d0741 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -129,14 +129,14 @@
/** @hide */
@TestApi
- @SuppressLint("ConcreteCollection")
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
public @Nullable ArrayList<AutofillId> getFieldIds() {
return mFieldIds;
}
/** @hide */
@TestApi
- @SuppressLint("ConcreteCollection")
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
public @Nullable ArrayList<AutofillValue> getFieldValues() {
return mFieldValues;
}
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index e35b8b7..ad6316c 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -119,6 +119,7 @@
IoUtils.closeQuietly(control.incremental.cmd);
IoUtils.closeQuietly(control.incremental.pendingReads);
IoUtils.closeQuietly(control.incremental.log);
+ IoUtils.closeQuietly(control.incremental.blocksWritten);
}
}
}
diff --git a/core/java/android/service/attestation/IImpressionAttestationService.aidl b/core/java/android/service/screenshot/IScreenshotHasherService.aidl
similarity index 66%
rename from core/java/android/service/attestation/IImpressionAttestationService.aidl
rename to core/java/android/service/screenshot/IScreenshotHasherService.aidl
index 5ff8f17..d14d147 100644
--- a/core/java/android/service/attestation/IImpressionAttestationService.aidl
+++ b/core/java/android/service/screenshot/IScreenshotHasherService.aidl
@@ -14,43 +14,43 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.service.screenshot;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.RemoteCallback;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
/**
- * Service used to handle impression attestation requests.
+ * Service used to handle ScreenshotHash requests.
*
* @hide
*/
-oneway interface IImpressionAttestationService {
+oneway interface IScreenshotHasherService {
/**
- * Generates the impression token that can be used to validate that the system generated the
+ * Generates the ScreenshotHash token that can be used to validate that the system generated the
* token.
*
* @param salt The salt to use when generating the hmac. This should be unique to the caller so
* the token cannot be verified by any other process.
* @param screenshot The screenshot to generate the hash and add to the token.
- * @param bounds The size and position of the content being attested in the window.
+ * @param bounds The size and position of the content being screenshot in the window.
* @param hashAlgorithm The String for the hashing algorithm to use based on values in
* {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS}.
- * @param Callback The callback invoked to send back the impression token.
+ * @param Callback The callback invoked to send back the ScreenshotHash token.
*/
- void generateImpressionToken(in byte[] salt, in HardwareBuffer screenshot, in Rect bounds,
+ void generateScreenshotHash(in byte[] salt, in HardwareBuffer screenshot, in Rect bounds,
in String hashAlgorithm, in RemoteCallback callback);
/**
- * Call to verify that the impressionToken passed in was generated by the system. The result
+ * Call to verify that the ScreenshotHash passed in was generated by the system. The result
* will be sent in the callback as a boolean with the key {@link #EXTRA_VERIFICATION_STATUS}.
*
* @param salt The salt value to use when verifying the hmac. This should be the same value that
- * was passed to {@link generateImpressionToken()} to generate the token.
- * @param impressionToken The token to verify that it was generated by the system.
+ * was passed to {@link generateScreenshotHash()} to generate the token.
+ * @param screenshotHash The hash to verify that it was generated by the system.
* @param callback The callback invoked to send back the verification status.
*/
- void verifyImpressionToken(in byte[] salt, in ImpressionToken impressionToken,
+ void verifyScreenshotHash(in byte[] salt, in ScreenshotHash screenshotHash,
in RemoteCallback callback);
}
diff --git a/core/java/android/service/screenshot/OWNERS b/core/java/android/service/screenshot/OWNERS
new file mode 100644
index 0000000..0862c05
--- /dev/null
+++ b/core/java/android/service/screenshot/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/service/screenshot/ScreenshotHash.aidl
similarity index 90%
rename from core/java/android/service/attestation/ImpressionToken.aidl
rename to core/java/android/service/screenshot/ScreenshotHash.aidl
index 284a4ba..a7c50db 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/service/screenshot/ScreenshotHash.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.service.screenshot;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable ScreenshotHash;
diff --git a/core/java/android/service/attestation/ImpressionToken.java b/core/java/android/service/screenshot/ScreenshotHash.java
similarity index 81%
rename from core/java/android/service/attestation/ImpressionToken.java
rename to core/java/android/service/screenshot/ScreenshotHash.java
index 4355dc0..9ae4192 100644
--- a/core/java/android/service/attestation/ImpressionToken.java
+++ b/core/java/android/service/screenshot/ScreenshotHash.java
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.service.screenshot;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -25,14 +25,14 @@
import com.android.internal.util.DataClass;
/**
- * The ads impression token used to validate information about what was present on screen.
+ * The screenshot hash used to validate information about what was present on screen.
* @hide
*
* TODO: Remove hide and SystemAPI since this will be a public class
*/
@SystemApi
@DataClass(genToString = true, genAidl = true)
-public final class ImpressionToken implements Parcelable {
+public final class ScreenshotHash implements Parcelable {
/**
* The timestamp when the screenshot was generated.
*/
@@ -42,23 +42,27 @@
* The bounds of the requested area to take the screenshot. This is in window space passed in
* by the client.
*/
- private @NonNull final Rect mBoundsInWindow;
+ @NonNull
+ private final Rect mBoundsInWindow;
/**
* The selected hashing algorithm that generated the image hash.
*/
- private @NonNull final String mHashingAlgorithm;
+ @NonNull
+ private final String mHashingAlgorithm;
/**
- * The image hash generated when creating the impression token from the screenshot taken.
+ * The image hash generated when creating the ScreenshotHash from the screenshot taken.
*/
- private @NonNull final byte[] mImageHash;
+ @NonNull
+ private final byte[] mImageHash;
/**
* The hmac generated by the system and used to verify whether this token was generated by
* the system. This should only be accessed by a system process.
*/
- private @NonNull final byte[] mHmac;
+ @NonNull
+ private final byte[] mHmac;
/**
* The hmac generated by the system and used to verify whether this token was generated by
@@ -67,19 +71,21 @@
* @hide
*/
@SystemApi
- public @NonNull byte[] getHmac() {
+ @NonNull
+ public byte[] getHmac() {
return mHmac;
}
- // Code below generated by codegen v1.0.18.
+ // 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/service/attestation/ImpressionToken.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/screenshot
+ // /ScreenshotHash.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -87,7 +93,7 @@
/**
- * Creates a new ImpressionToken.
+ * Creates a new ScreenshotHash.
*
* @param screenshotTimeMillis
* The timestamp when the screenshot was generated.
@@ -97,13 +103,13 @@
* @param hashingAlgorithm
* The selected hashing algorithm that generated the image hash.
* @param imageHash
- * The image hash generated when creating the impression token from the screenshot taken.
+ * The image hash generated when creating the ScreenshotHash from the screenshot taken.
* @param hmac
* The hmac generated by the system and used to verify whether this token was generated by
* the system. This should only be accessed by a system process.
*/
@DataClass.Generated.Member
- public ImpressionToken(
+ public ScreenshotHash(
long screenshotTimeMillis,
@NonNull Rect boundsInWindow,
@NonNull String hashingAlgorithm,
@@ -152,7 +158,7 @@
}
/**
- * The image hash generated when creating the impression token from the screenshot taken.
+ * The image hash generated when creating the ScreenshotHash from the screenshot taken.
*/
@DataClass.Generated.Member
public @NonNull byte[] getImageHash() {
@@ -165,7 +171,7 @@
// You can override field toString logic by defining methods like:
// String fieldNameToString() { ... }
- return "ImpressionToken { " +
+ return "ScreenshotHash { " +
"screenshotTimeMillis = " + mScreenshotTimeMillis + ", " +
"boundsInWindow = " + mBoundsInWindow + ", " +
"hashingAlgorithm = " + mHashingAlgorithm + ", " +
@@ -194,7 +200,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ ImpressionToken(@NonNull Parcel in) {
+ /* package-private */ ScreenshotHash(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -222,24 +228,24 @@
}
@DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<ImpressionToken> CREATOR
- = new Parcelable.Creator<ImpressionToken>() {
+ public static final @NonNull Parcelable.Creator<ScreenshotHash> CREATOR
+ = new Parcelable.Creator<ScreenshotHash>() {
@Override
- public ImpressionToken[] newArray(int size) {
- return new ImpressionToken[size];
+ public ScreenshotHash[] newArray(int size) {
+ return new ScreenshotHash[size];
}
@Override
- public ImpressionToken createFromParcel(@NonNull Parcel in) {
- return new ImpressionToken(in);
+ public ScreenshotHash createFromParcel(@NonNull Parcel in) {
+ return new ScreenshotHash(in);
}
};
@DataClass.Generated(
- time = 1604539951959L,
- codegenVersion = "1.0.18",
- sourceFile = "frameworks/base/core/java/android/service/attestation/ImpressionToken.java",
- inputSignatures = "private final long mScreenshotTimeMillis\nprivate final @android.annotation.NonNull android.graphics.Rect mBoundsInWindow\nprivate final @android.annotation.NonNull java.lang.String mHashingAlgorithm\nprivate final @android.annotation.NonNull byte[] mImageHash\nprivate final @android.annotation.NonNull byte[] mHmac\npublic @android.annotation.SystemApi @android.annotation.NonNull byte[] getHmac()\nclass ImpressionToken extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genAidl=true)")
+ time = 1612383172822L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/service/screenshot/ScreenshotHash.java",
+ inputSignatures = "private final long mScreenshotTimeMillis\nprivate final @android.annotation.NonNull android.graphics.Rect mBoundsInWindow\nprivate final @android.annotation.NonNull java.lang.String mHashingAlgorithm\nprivate final @android.annotation.NonNull byte[] mImageHash\nprivate final @android.annotation.NonNull byte[] mHmac\npublic @android.annotation.SystemApi @android.annotation.NonNull byte[] getHmac()\nclass ScreenshotHash extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/screenshot/ScreenshotHasherService.java b/core/java/android/service/screenshot/ScreenshotHasherService.java
new file mode 100644
index 0000000..d96cc7e
--- /dev/null
+++ b/core/java/android/service/screenshot/ScreenshotHasherService.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.screenshot;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallback;
+
+/**
+ * A service that handles generating and verify {@link ScreenshotHash}.
+ *
+ * The service will generate a ScreenshotHash based on arguments passed in. Then later that
+ * same ScreenshotHash can be verified to determine that it was created by the system.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class ScreenshotHasherService extends Service {
+ /** @hide **/
+ public static final String EXTRA_SCREENSHOT_HASH =
+ "android.service.screenshot.extra.SCREENSHOT_HASH";
+
+ /** @hide **/
+ public static final String EXTRA_VERIFICATION_STATUS =
+ "android.service.screenshot.extra.VERIFICATION_STATUS";
+
+ /**
+ * Manifest metadata key for the resource string array containing the names of all hashing
+ * algorithms provided by the service.
+ *
+ * @hide
+ */
+ public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
+ "android.screenshot.available_algorithms";
+
+ /**
+ * The {@link Intent} action that must be declared as handled by a service in its manifest
+ * for the system to recognize it as a ScreenshotHash providing service.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String SERVICE_INTERFACE =
+ "android.service.screenshot.ScreenshotHasherService";
+
+ private ScreenshotHasherServiceWrapper mWrapper;
+ private Handler mHandler;
+
+ public ScreenshotHasherService() {
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWrapper = new ScreenshotHasherServiceWrapper();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ @NonNull
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ return mWrapper;
+ }
+
+ /**
+ * Generates the ScreenshotHash that can be used to validate that the system generated the
+ * token.
+ *
+ * @param salt The salt to use when generating the hmac. This should be unique to the
+ * caller so the token cannot be verified by any other process.
+ * @param screenshot The screenshot buffer for the content.
+ * @param bounds The size and position of the content being screenshot in the window.
+ * @param hashAlgorithm The String for the hashing algorithm to use based values in
+ * {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
+ * @return A ScreenshotHash that can be used to validate information about the content.
+ * Returns null when the arguments sent are invalid.
+ */
+ @Nullable
+ public abstract ScreenshotHash onGenerateScreenshotHash(@NonNull byte[] salt,
+ @NonNull HardwareBuffer screenshot, @NonNull Rect bounds,
+ @NonNull String hashAlgorithm);
+
+ /**
+ * Call to verify that the ScreenshotHash passed in was generated by the system.
+ *
+ * @param salt The salt value to use when verifying the hmac. This should be the
+ * same value that was passed to
+ * {@link #onGenerateScreenshotHash(byte[],
+ * HardwareBuffer, Rect, String)} to
+ * generate the token.
+ * @param screenshotHash The token to verify that it was generated by the system.
+ * @return true if the token can be verified that it was generated by the system.
+ */
+ public abstract boolean onVerifyScreenshotHash(@NonNull byte[] salt,
+ @NonNull ScreenshotHash screenshotHash);
+
+ private void generateScreenshotHash(byte[] salt, HardwareBuffer screenshot, Rect bounds,
+ String hashAlgorithm, RemoteCallback callback) {
+ ScreenshotHash screenshotHash = onGenerateScreenshotHash(salt, screenshot,
+ bounds,
+ hashAlgorithm);
+ final Bundle data = new Bundle();
+ data.putParcelable(EXTRA_SCREENSHOT_HASH, screenshotHash);
+ callback.sendResult(data);
+ }
+
+ private void verifyScreenshotHash(byte[] salt, ScreenshotHash screenshotHash,
+ RemoteCallback callback) {
+ boolean verificationStatus = onVerifyScreenshotHash(salt, screenshotHash);
+ final Bundle data = new Bundle();
+ data.putBoolean(EXTRA_VERIFICATION_STATUS, verificationStatus);
+ callback.sendResult(data);
+ }
+
+ private final class ScreenshotHasherServiceWrapper extends
+ IScreenshotHasherService.Stub {
+ @Override
+ public void generateScreenshotHash(byte[] salt, HardwareBuffer screenshot, Rect bounds,
+ String hashAlgorithm, RemoteCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(ScreenshotHasherService::generateScreenshotHash,
+ ScreenshotHasherService.this, salt, screenshot, bounds,
+ hashAlgorithm, callback));
+ }
+
+ @Override
+ public void verifyScreenshotHash(byte[] salt, ScreenshotHash screenshotHash,
+ RemoteCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(ScreenshotHasherService::verifyScreenshotHash,
+ ScreenshotHasherService.this, salt, screenshotHash,
+ callback));
+ }
+ }
+}
diff --git a/core/java/android/service/smartspace/ISmartspaceService.aidl b/core/java/android/service/smartspace/ISmartspaceService.aidl
new file mode 100644
index 0000000..c9c6807
--- /dev/null
+++ b/core/java/android/service/smartspace/ISmartspaceService.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to Smartspace service.
+ *
+ * @hide
+ */
+oneway interface ISmartspaceService {
+
+ void onCreateSmartspaceSession(in SmartspaceConfig context, in SmartspaceSessionId sessionId);
+
+ void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+ void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+ void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void onDestroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/service/smartspace/OWNERS b/core/java/android/service/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/service/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
new file mode 100644
index 0000000..09b7310
--- /dev/null
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.smartspace;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService.Stub;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A service used to share the lifecycle of smartspace UI (open, close, interaction)
+ * and also to return smartspace result on a query.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SmartspaceService extends Service {
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ *
+ * <p>The service must also require the {@link android.permission#MANAGE_SMARTSPACE}
+ * permission.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.smartspace.SmartspaceService";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "SmartspaceService";
+ private final ArrayMap<SmartspaceSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+ new ArrayMap<>();
+ private Handler mHandler;
+
+ private final android.service.smartspace.ISmartspaceService mInterface = new Stub() {
+
+ @Override
+ public void onCreateSmartspaceSession(SmartspaceConfig smartspaceConfig,
+ SmartspaceSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doCreateSmartspaceSession,
+ SmartspaceService.this, smartspaceConfig, sessionId));
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+ SmartspaceTargetEvent event) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::notifySmartspaceEvent,
+ SmartspaceService.this, sessionId, event));
+ }
+
+ @Override
+ public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doRequestPredictionUpdate,
+ SmartspaceService.this, sessionId));
+ }
+
+ @Override
+ public void registerSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doRegisterSmartspaceUpdates,
+ SmartspaceService.this, sessionId, callback));
+ }
+
+ @Override
+ public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doUnregisterSmartspaceUpdates,
+ SmartspaceService.this, sessionId, callback));
+ }
+
+ @Override
+ public void onDestroySmartspaceSession(SmartspaceSessionId sessionId) {
+
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doDestroy,
+ SmartspaceService.this, sessionId));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ @Override
+ @NonNull
+ public final IBinder onBind(@NonNull Intent intent) {
+ Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks);
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Slog.w(TAG, "Tried to bind to wrong intent (should be "
+ + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+
+ private void doCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+ @NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+ mSessionCallbacks.put(sessionId, new ArrayList<>());
+ onCreateSmartspaceSession(config, sessionId);
+ }
+
+ /**
+ * Gets called when the client calls <code> SmartspaceManager#createSmartspaceSession </code>.
+ */
+ public abstract void onCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+ @NonNull SmartspaceSessionId sessionId);
+
+ /**
+ * Gets called when the client calls <code> SmartspaceSession#notifySmartspaceEvent </code>.
+ */
+ @MainThread
+ public abstract void notifySmartspaceEvent(@NonNull SmartspaceSessionId sessionId,
+ @NonNull SmartspaceTargetEvent event);
+
+ /**
+ * Gets called when the client calls <code> SmartspaceSession#requestSmartspaceUpdate </code>.
+ */
+ @MainThread
+ public abstract void onRequestSmartspaceUpdate(@NonNull SmartspaceSessionId sessionId);
+
+ private void doRegisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper == null) {
+ callbacks.add(new CallbackWrapper(callback,
+ callbackWrapper ->
+ mHandler.post(
+ () -> removeCallbackWrapper(callbacks, callbackWrapper))));
+ }
+ }
+
+ private void doUnregisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper != null) {
+ removeCallbackWrapper(callbacks, wrapper);
+ }
+ }
+
+ private void doRequestPredictionUpdate(@NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks);
+ // Just an optimization, if there are no callbacks, then don't bother notifying the service
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null && !callbacks.isEmpty()) {
+ onRequestSmartspaceUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Finds the callback wrapper for the given callback.
+ */
+ private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+ ISmartspaceCallback callback) {
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ if (callbacks.get(i).isCallback(callback)) {
+ return callbacks.get(i);
+ }
+ }
+ return null;
+ }
+
+ private void removeCallbackWrapper(
+ ArrayList<CallbackWrapper> callbacks, CallbackWrapper wrapper) {
+ if (callbacks == null) {
+ return;
+ }
+ callbacks.remove(wrapper);
+ }
+
+ /**
+ * Gets called when the client calls <code> SmartspaceManager#destroy() </code>.
+ */
+ public abstract void onDestroySmartspaceSession(@NonNull SmartspaceSessionId sessionId);
+
+ private void doDestroy(@NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks);
+ super.onDestroy();
+ mSessionCallbacks.remove(sessionId);
+ onDestroySmartspaceSession(sessionId);
+ }
+
+ /**
+ * Used by the prediction factory to send back results the client app. The can be called
+ * in response to {@link #onRequestSmartspaceUpdate(SmartspaceSessionId)} or proactively as
+ * a result of changes in predictions.
+ */
+ public final void updateSmartspaceTargets(@NonNull SmartspaceSessionId sessionId,
+ @NonNull List<SmartspaceTarget> targets) {
+ Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks);
+ List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null) {
+ for (CallbackWrapper callback : callbacks) {
+ callback.accept(targets);
+ }
+ }
+ }
+
+ /**
+ * Destroys a smartspace session.
+ */
+ @MainThread
+ public abstract void onDestroy(@NonNull SmartspaceSessionId sessionId);
+
+ private static final class CallbackWrapper implements Consumer<List<SmartspaceTarget>>,
+ IBinder.DeathRecipient {
+
+ private final Consumer<CallbackWrapper> mOnBinderDied;
+ private ISmartspaceCallback mCallback;
+
+ CallbackWrapper(ISmartspaceCallback callback,
+ @Nullable Consumer<CallbackWrapper> onBinderDied) {
+ mCallback = callback;
+ mOnBinderDied = onBinderDied;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death: " + e);
+ }
+ }
+
+ public boolean isCallback(@NonNull ISmartspaceCallback callback) {
+ if (mCallback == null) {
+ Slog.e(TAG, "Callback is null, likely the binder has died.");
+ return false;
+ }
+ return mCallback.equals(callback);
+ }
+
+ @Override
+ public void accept(List<SmartspaceTarget> smartspaceTargets) {
+ try {
+ if (mCallback != null) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "CallbackWrapper.accept smartspaceTargets=" + smartspaceTargets);
+ }
+ mCallback.onResult(new ParceledListSlice(smartspaceTargets));
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result:" + e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ mCallback = null;
+ if (mOnBinderDied != null) {
+ mOnBinderDied.accept(this);
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 68d6f3f..25f8090 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -345,7 +345,8 @@
*/
@SystemApi
@HotwordConfigResult
- public final int setHotwordDetectionConfig(@Nullable Bundle options) {
+ public final int setHotwordDetectionConfig(
+ @SuppressLint("NullableCollection") @Nullable Bundle options) {
if (mSystemService == null) {
throw new IllegalStateException("Not available until onReady() is called");
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 507dc7a..82e0b4a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -880,8 +880,8 @@
InputChannel inputChannel = new InputChannel();
if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
- mDisplay.getDisplayId(), mInsetsState, mWinFrames.frame,
- inputChannel, mInsetsState, mTempControls) < 0) {
+ mDisplay.getDisplayId(), mInsetsState, inputChannel, mInsetsState,
+ mTempControls) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
@@ -1494,6 +1494,16 @@
private void doDetachEngine() {
mActiveEngines.remove(mEngine);
mEngine.detach();
+ // Some wallpapers will not trigger the rendering threads of the remaining engines even
+ // if they are visible, so we need to toggle the state to get their attention.
+ if (!mDetached.get()) {
+ for (Engine eng : mActiveEngines) {
+ if (eng.mVisible) {
+ eng.doVisibilityChanged(false);
+ eng.doVisibilityChanged(true);
+ }
+ }
+ }
}
@Override
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b229212..790773f 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -65,7 +65,7 @@
DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true");
DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
- DEFAULT_FLAGS.put("settings_silky_home", "false");
+ DEFAULT_FLAGS.put("settings_silky_home", "true");
DEFAULT_FLAGS.put("settings_contextual_home", "false");
DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
}
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 86120d1..6718e93 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -16,6 +16,7 @@
package android.util;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
@@ -23,6 +24,8 @@
import libcore.util.EmptyArray;
+import java.util.Objects;
+
/**
* <code>SparseArray</code> maps integers to Objects and, unlike a normal array of Objects,
* its indices can contain gaps. <code>SparseArray</code> is intended to be more memory-efficient
@@ -505,4 +508,44 @@
buffer.append('}');
return buffer.toString();
}
+
+ /**
+ * For backwards compatibility reasons, {@link Object#equals(Object)} cannot be implemented,
+ * so this serves as a manually invoked alternative.
+ */
+ public boolean contentEquals(@Nullable SparseArray<E> other) {
+ if (other == null) {
+ return false;
+ }
+
+ int size = size();
+ if (size != other.size()) {
+ return false;
+ }
+
+ for (int index = 0; index < size; index++) {
+ int key = keyAt(index);
+ if (!Objects.equals(valueAt(index), other.get(key))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * For backwards compatibility, {@link Object#hashCode()} cannot be implemented, so this serves
+ * as a manually invoked alternative.
+ */
+ public int contentHashCode() {
+ int hash = 0;
+ int size = size();
+ for (int index = 0; index < size; index++) {
+ int key = keyAt(index);
+ E value = valueAt(index);
+ hash = 31 * hash + Objects.hashCode(key);
+ hash = 31 * hash + Objects.hashCode(value);
+ }
+ return hash;
+ }
}
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index 723f1dd..49ff237 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -28,6 +28,8 @@
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;
+import com.android.internal.inputmethod.Completable;
+import com.android.internal.inputmethod.ResultCallbacks;
import com.android.internal.view.IInputMethodManager;
import java.io.PrintWriter;
@@ -91,7 +93,9 @@
* @param where
*/
public void sendToService(byte[] protoDump, int source, String where) throws RemoteException {
- mService.startProtoDump(protoDump, source, where);
+ final Completable.Void value = Completable.createVoid();
+ mService.startProtoDump(protoDump, source, where, ResultCallbacks.of(value));
+ Completable.getResult(value);
}
/**
diff --git a/core/java/android/util/imetracing/OWNERS b/core/java/android/util/imetracing/OWNERS
new file mode 100644
index 0000000..885fd0a
--- /dev/null
+++ b/core/java/android/util/imetracing/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/view/ContentInfo.java b/core/java/android/view/ContentInfo.java
index bc66ea1..547bc9d 100644
--- a/core/java/android/view/ContentInfo.java
+++ b/core/java/android/view/ContentInfo.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -204,6 +205,7 @@
* the IME.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
@@ -347,7 +349,7 @@
* @return this builder
*/
@NonNull
- public Builder setExtras(@Nullable Bundle extras) {
+ public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
mExtras = extras;
return this;
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index c664ccb..4168064 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -25,8 +25,8 @@
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.KeyguardManager;
+import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -59,12 +59,8 @@
* an application window, excluding the system decorations. The application display area may
* be smaller than the real display area because the system subtracts the space needed
* for decor elements such as the status bar. Use {@link WindowMetrics#getBounds()} to query the
- * application window bounds.</li>
- * <li>The real display area specifies the part of the display that contains content
- * including the system decorations. Even so, the real display area may be smaller than the
- * physical size of the display if the window manager is emulating a smaller display
- * using (adb shell wm size). Use the following methods to query the
- * real display area: {@link #getRealSize}, {@link #getRealMetrics}.</li>
+ * application window bounds. Generally, use {@link WindowManager#getCurrentWindowMetrics()} to
+ * query the metrics and perform UI-related actions.</li>
* </ul>
* </p><p>
* A logical display does not necessarily represent a particular physical display device
@@ -677,9 +673,9 @@
@UnsupportedAppUsage
public DisplayAdjustments getDisplayAdjustments() {
if (mResources != null) {
- final DisplayAdjustments currentAdjustements = mResources.getDisplayAdjustments();
- if (!mDisplayAdjustments.equals(currentAdjustements)) {
- mDisplayAdjustments = new DisplayAdjustments(currentAdjustements);
+ final DisplayAdjustments currentAdjustments = mResources.getDisplayAdjustments();
+ if (!mDisplayAdjustments.equals(currentAdjustments)) {
+ mDisplayAdjustments = new DisplayAdjustments(currentAdjustments);
}
}
@@ -900,6 +896,32 @@
}
/**
+ * Returns the {@link RoundedCorner} of the given position if there is one.
+ *
+ * @param position the position of the rounded corner on the display.
+ *
+ * @return the rounded corner of the given position. Returns {@code null} if there is none.
+ */
+ @SuppressLint("VisiblySynchronized")
+ @Nullable
+ public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ RoundedCorners roundedCorners;
+ if (mMayAdjustByFixedRotation) {
+ roundedCorners = getDisplayAdjustments().adjustRoundedCorner(
+ mDisplayInfo.roundedCorners,
+ mDisplayInfo.rotation,
+ mDisplayInfo.logicalWidth,
+ mDisplayInfo.logicalHeight);
+ } else {
+ roundedCorners = mDisplayInfo.roundedCorners;
+ }
+ return roundedCorners == null ? null : roundedCorners.getRoundedCorner(position);
+ }
+ }
+
+ /**
* Gets the pixel format of the display.
* @return One of the constants defined in {@link android.graphics.PixelFormat}.
*
@@ -1191,30 +1213,34 @@
}
/**
- * Gets the real size of the display without subtracting any window decor or
- * applying any compatibility scale factors.
+ * Provides the largest {@link Point outSize} an app may expect in the current system state,
+ * without subtracting any window decor.
* <p>
- * The size is adjusted based on the current rotation of the display.
+ * The size describes the largest potential area the window might occupy. The size is adjusted
+ * based on the current rotation of the display.
* </p><p>
* The real size may be smaller than the physical size of the screen when the
* window manager is emulating a smaller display (using adb shell wm size).
- * </p><p>
- * In general, {@link #getRealSize(Point)} and {@link WindowManager#getMaximumWindowMetrics()}
- * report the same bounds except that certain areas of the display may not be available to
- * windows created in the {@link WindowManager}'s {@link Context}.
- *
- * For example, imagine a device which has a multi-task mode that limits windows to half of the
- * screen. In this case, {@link WindowManager#getMaximumWindowMetrics()} reports the
- * bounds of the screen half where the window is located, while {@link #getRealSize(Point)}
- * still reports the bounds of the whole display.
+ * </p>
*
* @param outSize Set to the real size of the display.
- *
- * @see WindowManager#getMaximumWindowMetrics()
*/
public void getRealSize(Point outSize) {
synchronized (this) {
updateDisplayInfoLocked();
+ if (shouldReportMaxBounds()) {
+ final Rect bounds = mResources.getConfiguration()
+ .windowConfiguration.getMaxBounds();
+ outSize.x = bounds.width();
+ outSize.y = bounds.height();
+ if (DEBUG) {
+ Log.d(TAG, "getRealSize determined from max bounds: " + outSize
+ + " for uid " + Process.myUid());
+ }
+ // Skip adjusting by fixed rotation, since if it is necessary, the configuration
+ // should already reflect the expected rotation.
+ return;
+ }
outSize.x = mDisplayInfo.logicalWidth;
outSize.y = mDisplayInfo.logicalHeight;
if (mMayAdjustByFixedRotation) {
@@ -1224,9 +1250,11 @@
}
/**
- * Gets display metrics based on the real size of this display.
+ * Provides the largest {@link DisplayMetrics outMetrics} an app may expect in the current
+ * system state, without subtracting any window decor.
* <p>
- * The size is adjusted based on the current rotation of the display.
+ * The size describes the largest potential area the window might occupy. The size is adjusted
+ * based on the current rotation of the display.
* </p><p>
* The real size may be smaller than the physical size of the screen when the
* window manager is emulating a smaller display (using adb shell wm size).
@@ -1237,6 +1265,18 @@
public void getRealMetrics(DisplayMetrics outMetrics) {
synchronized (this) {
updateDisplayInfoLocked();
+ if (shouldReportMaxBounds()) {
+ mDisplayInfo.getMaxBoundsMetrics(outMetrics,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,
+ mResources.getConfiguration());
+ if (DEBUG) {
+ Log.d(TAG, "getRealMetrics determined from max bounds: " + outMetrics
+ + " for uid " + Process.myUid());
+ }
+ // Skip adjusting by fixed rotation, since if it is necessary, the configuration
+ // should already reflect the expected rotation.
+ return;
+ }
mDisplayInfo.getLogicalMetrics(outMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
if (mMayAdjustByFixedRotation) {
@@ -1246,6 +1286,20 @@
}
/**
+ * Determines if {@link WindowConfiguration#getMaxBounds()} should be reported as the
+ * display dimensions. The max bounds field may be smaller than the logical dimensions
+ * when apps need to be sandboxed.
+ * @return {@code true} when max bounds should be applied.
+ */
+ private boolean shouldReportMaxBounds() {
+ if (mResources == null) {
+ return false;
+ }
+ final Configuration config = mResources.getConfiguration();
+ return config != null && !config.windowConfiguration.getMaxBounds().isEmpty();
+ }
+
+ /**
* Gets the state of the display, such as whether it is on or off.
*
* @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index 5d5771c..e307eff 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -151,6 +151,23 @@
: realCutout;
}
+ /**
+ * Returns the adjusted {@link RoundedCorners} if available. Otherwise the original
+ * {@link RoundedCorners} is returned.
+ */
+ @Nullable
+ public RoundedCorners adjustRoundedCorner(@Nullable RoundedCorners realRoundedCorners,
+ @Surface.Rotation int realRotation, int displayWidth, int displayHeight) {
+ final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+ if (realRoundedCorners == null || rotationAdjustments == null
+ || rotationAdjustments.mRotation == realRotation) {
+ return realRoundedCorners;
+ }
+
+ return realRoundedCorners.rotate(
+ rotationAdjustments.mRotation, displayWidth, displayHeight);
+ }
+
/** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */
@Surface.Rotation
public int getRotation(@Surface.Rotation int realRotation) {
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index d200a328..8a44504 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -24,6 +24,7 @@
import static android.view.DisplayInfoProto.NAME;
import android.annotation.Nullable;
+import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -301,6 +302,12 @@
*/
public float brightnessDefault;
+ /**
+ * The {@link RoundedCorners} if present, otherwise {@code null}.
+ */
+ @Nullable
+ public RoundedCorners roundedCorners;
+
public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
public DisplayInfo createFromParcel(Parcel source) {
@@ -369,7 +376,8 @@
&& refreshRateOverride == other.refreshRateOverride
&& brightnessMinimum == other.brightnessMinimum
&& brightnessMaximum == other.brightnessMaximum
- && brightnessDefault == other.brightnessDefault;
+ && brightnessDefault == other.brightnessDefault
+ && Objects.equals(roundedCorners, other.roundedCorners);
}
@Override
@@ -418,6 +426,7 @@
brightnessMinimum = other.brightnessMinimum;
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
+ roundedCorners = other.roundedCorners;
}
public void readFromParcel(Parcel source) {
@@ -468,6 +477,7 @@
brightnessMinimum = source.readFloat();
brightnessMaximum = source.readFloat();
brightnessDefault = source.readFloat();
+ roundedCorners = source.readTypedObject(RoundedCorners.CREATOR);
}
@Override
@@ -517,6 +527,7 @@
dest.writeFloat(brightnessMinimum);
dest.writeFloat(brightnessMaximum);
dest.writeFloat(brightnessDefault);
+ dest.writeTypedObject(roundedCorners, flags);
}
@Override
@@ -605,11 +616,29 @@
getMetricsWithSize(outMetrics, ci, configuration, appWidth, appHeight);
}
+ /**
+ * Populates {@code outMetrics} with details of the logical display. Bounds are limited
+ * by the logical size of the display.
+ *
+ * @param outMetrics the {@link DisplayMetrics} to be populated
+ * @param compatInfo the {@link CompatibilityInfo} to be applied
+ * @param configuration the {@link Configuration}
+ */
public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
Configuration configuration) {
getMetricsWithSize(outMetrics, compatInfo, configuration, logicalWidth, logicalHeight);
}
+ /**
+ * Similar to {@link #getLogicalMetrics}, but the limiting bounds are determined from
+ * {@link WindowConfiguration#getMaxBounds()}
+ */
+ public void getMaxBoundsMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+ Configuration configuration) {
+ Rect bounds = configuration.windowConfiguration.getMaxBounds();
+ getMetricsWithSize(outMetrics, compatInfo, configuration, bounds.width(), bounds.height());
+ }
+
public int getNaturalWidth() {
return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ?
logicalWidth : logicalHeight;
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index c2566cc..5937499 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -149,7 +149,7 @@
* <p>
* The time value that was used in all the vsync listeners and drawing for
* the frame (Choreographer frame callbacks, animations,
- * {@link View#getDrawingTime()}, etc…)
+ * {@link View#getDrawingTime()}, etc.)
* </p>
*/
public static final int VSYNC_TIMESTAMP = 11;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 68a6de8..7843411 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -32,7 +32,7 @@
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
import android.view.DisplayCutout;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
@@ -765,21 +765,21 @@
/**
* Gets an array of support hashing algorithms that can be used to generate the hash of the
* screenshot. The String value of one algorithm should be used when requesting to generate
- * the impression attestation token.
+ * the ScreenshotHash.
*
* @return a String array of supported hashing algorithms.
*/
- String[] getSupportedImpressionAlgorithms();
+ String[] getSupportedScreenshotHashingAlgorithms();
/**
- * Validate the impression token was generated by the system. The impression token passed in
- * should be the token generated when calling {@link IWindowSession#generateImpressionToken}
+ * Validate the ScreenshotHash was generated by the system. The ScreenshotHash passed in
+ * should be the token generated when calling {@link IWindowSession#generateScreenshotHash}
*
- * @param impressionToken The token to verify that it was generated by the system.
+ * @param ScreenshotHash The token to verify that it was generated by the system.
* @return true if the token was generated by the system or false if the token cannot be
* verified.
*/
- boolean verifyImpressionToken(in ImpressionToken impressionToken);
+ boolean verifyScreenshotHash(in ScreenshotHash screenshotHash);
/**
* Registers a listener for a {@link android.app.WindowContext} to handle configuration changes
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 85498cb..990b7bd 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -22,7 +22,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.InputChannel;
@@ -47,11 +47,11 @@
interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in InsetsState requestedVisibility,
- out Rect outFrame, out InputChannel outInputChannel, out InsetsState insetsState,
+ out InputChannel outInputChannel, out InsetsState insetsState,
out InsetsSourceControl[] activeControls);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId,
- in InsetsState requestedVisibility, out Rect outFrame, out InputChannel outInputChannel,
+ in InsetsState requestedVisibility, out InputChannel outInputChannel,
out InsetsState insetsState, out InsetsSourceControl[] activeControls);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out InsetsState insetsState);
@@ -345,14 +345,14 @@
void grantEmbeddedWindowFocus(IWindow window, in IBinder inputToken, boolean grantFocus);
/**
- * Generates an impression token that can be used to validate whether specific content was on
+ * Generates an ScreenshotHash that can be used to validate whether specific content was on
* screen.
*
- * @param window The token for the window where the view to attest is shown.
+ * @param window The token for the window where the view to screenshot is shown.
* @param boundsInWindow The size and position of the ads view in the window
* @param hashAlgorithm The String for the hashing algorithm to use based on values returned
- * from {@link IWindowManager#getSupportedImpressionAlgorithms()}
+ * from {@link IWindowManager#getSupportedHashingAlgorithms()}
*/
- ImpressionToken generateImpressionToken(IWindow window, in Rect boundsInWindow,
+ ScreenshotHash generateScreenshotHash(IWindow window, in Rect boundsInWindow,
in String hashAlgorithm);
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f4b90e1..59e4931 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -22,6 +22,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.Battery;
import android.hardware.SensorManager;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
@@ -73,6 +74,7 @@
private final boolean mHasMicrophone;
private final boolean mHasButtonUnderPad;
private final boolean mHasSensor;
+ private final boolean mHasBattery;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@GuardedBy("mMotionRanges")
@@ -84,6 +86,9 @@
@GuardedBy("mMotionRanges")
private SensorManager mSensorManager;
+ @GuardedBy("mMotionRanges")
+ private Battery mBattery;
+
/**
* A mask for input source classes.
*
@@ -323,6 +328,13 @@
public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
/**
+ * The input source is a sensor associated with the input device.
+ *
+ * @see #SOURCE_CLASS_NONE
+ */
+ public static final int SOURCE_SENSOR = 0x04000000 | SOURCE_CLASS_NONE;
+
+ /**
* A special input source constant that is used when filtering input devices
* to match devices that provide any type of input source.
*/
@@ -443,12 +455,11 @@
* Called by native code
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@VisibleForTesting
public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
- boolean hasButtonUnderPad, boolean hasSensor) {
+ boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -464,6 +475,7 @@
mHasMicrophone = hasMicrophone;
mHasButtonUnderPad = hasButtonUnderPad;
mHasSensor = hasSensor;
+ mHasBattery = hasBattery;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
}
@@ -483,6 +495,7 @@
mHasMicrophone = in.readInt() != 0;
mHasButtonUnderPad = in.readInt() != 0;
mHasSensor = in.readInt() != 0;
+ mHasBattery = in.readInt() != 0;
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
int numRanges = in.readInt();
@@ -830,6 +843,22 @@
}
/**
+ * Gets the battery object associated with the device, if there is one.
+ * Even if the device does not have a battery, the result is never null.
+ * Use {@link Battery#hasBattery} to determine whether a battery is
+ * present.
+ *
+ * @return The battery object associated with the device, never null.
+ */
+ @NonNull
+ public Battery getBattery() {
+ if (mBattery == null) {
+ mBattery = InputManager.getInstance().getInputDeviceBattery(mId, mHasBattery);
+ }
+ return mBattery;
+ }
+
+ /**
* Gets the sensor manager service associated with the input device.
* Even if the device does not have a sensor, the result is never null.
* Use {@link SensorManager#getSensorList} to get a full list of all supported sensors.
@@ -1051,6 +1080,7 @@
out.writeInt(mHasMicrophone ? 1 : 0);
out.writeInt(mHasButtonUnderPad ? 1 : 0);
out.writeInt(mHasSensor ? 1 : 0);
+ out.writeInt(mHasBattery ? 1 : 0);
final int numRanges = mMotionRanges.size();
out.writeInt(numRanges);
@@ -1097,6 +1127,8 @@
description.append(" Has Sensor: ").append(mHasSensor).append("\n");
+ description.append(" Has battery: ").append(mHasBattery).append("\n");
+
description.append(" Has mic: ").append(mHasMicrophone).append("\n");
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index b4e1172..e681c0e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -653,6 +653,7 @@
private void updateState(InsetsState newState) {
mState.setDisplayFrame(newState.getDisplayFrame());
mState.setDisplayCutout(newState.getDisplayCutout());
+ mState.setRoundedCorners(newState.getRoundedCorners());
@InsetsType int disabledUserAnimationTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bf377b0..fe6b6e4 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -106,7 +106,9 @@
public static final int ITYPE_NAVIGATION_BAR = 1;
public static final int ITYPE_CAPTION_BAR = 2;
- public static final int ITYPE_TOP_GESTURES = 3;
+ // The always visible types are visible to all windows regardless of the z-order.
+ public static final int FIRST_ALWAYS_VISIBLE_TYPE = 3;
+ public static final int ITYPE_TOP_GESTURES = FIRST_ALWAYS_VISIBLE_TYPE;
public static final int ITYPE_BOTTOM_GESTURES = 4;
public static final int ITYPE_LEFT_GESTURES = 5;
public static final int ITYPE_RIGHT_GESTURES = 6;
@@ -117,15 +119,16 @@
public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
- public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 11;
- public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 12;
- public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 13;
- public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 14;
+ public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11;
+ public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
+ public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
+ public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
+ public static final int LAST_ALWAYS_VISIBLE_TYPE = ITYPE_BOTTOM_DISPLAY_CUTOUT;
- public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 15;
- public static final int ITYPE_TOP_DISPLAY_CUTOUT = 16;
- public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 17;
- public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 18;
+ public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
+ public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
+ public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
+ public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
/** Input method window. */
public static final int ITYPE_IME = 19;
@@ -170,6 +173,9 @@
private final DisplayCutout.ParcelableWrapper mDisplayCutout =
new DisplayCutout.ParcelableWrapper();
+ /** The rounded corners on the display */
+ private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+
public InsetsState() {
}
@@ -182,6 +188,18 @@
}
/**
+ * Mirror the always visible sources from the other state. They will share the same object for
+ * the always visible types.
+ *
+ * @param other the state to mirror the mirrored sources from.
+ */
+ public void mirrorAlwaysVisibleInsetsSources(InsetsState other) {
+ for (int type = FIRST_ALWAYS_VISIBLE_TYPE; type <= LAST_ALWAYS_VISIBLE_TYPE; type++) {
+ mSources[type] = other.mSources[type];
+ }
+ }
+
+ /**
* Calculates {@link WindowInsets} based on the current source configuration.
*
* @param frame The frame to calculate the insets relative to.
@@ -241,7 +259,8 @@
}
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
- alwaysConsumeSystemBars, calculateRelativeCutout(frame), compatInsetsTypes,
+ alwaysConsumeSystemBars, calculateRelativeCutout(frame),
+ calculateRelativeRoundedCorners(frame), compatInsetsTypes,
(legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
}
@@ -266,6 +285,20 @@
return raw.inset(insetLeft, insetTop, insetRight, insetBottom);
}
+ private RoundedCorners calculateRelativeRoundedCorners(Rect frame) {
+ if (mDisplayFrame.equals(frame)) {
+ return mRoundedCorners;
+ }
+ if (frame == null) {
+ return RoundedCorners.NO_ROUNDED_CORNERS;
+ }
+ final int insetLeft = frame.left - mDisplayFrame.left;
+ final int insetTop = frame.top - mDisplayFrame.top;
+ final int insetRight = mDisplayFrame.right - frame.right;
+ final int insetBottom = mDisplayFrame.bottom - frame.bottom;
+ return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom);
+ }
+
public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -447,6 +480,14 @@
return mDisplayCutout.get();
}
+ public void setRoundedCorners(RoundedCorners roundedCorners) {
+ mRoundedCorners = roundedCorners;
+ }
+
+ public RoundedCorners getRoundedCorners() {
+ return mRoundedCorners;
+ }
+
/**
* Modifies the state of this class to exclude a certain type to make it ready for dispatching
* to the client.
@@ -478,6 +519,7 @@
public void scale(float scale) {
mDisplayFrame.scale(scale);
mDisplayCutout.scale(scale);
+ mRoundedCorners = mRoundedCorners.scale(scale);
for (int i = 0; i < SIZE; i++) {
final InsetsSource source = mSources[i];
if (source != null) {
@@ -497,6 +539,7 @@
public void set(InsetsState other, boolean copySources) {
mDisplayFrame.set(other.mDisplayFrame);
mDisplayCutout.set(other.mDisplayCutout);
+ mRoundedCorners = other.getRoundedCorners();
if (copySources) {
for (int i = 0; i < SIZE; i++) {
InsetsSource source = other.mSources[i];
@@ -604,11 +647,15 @@
}
public void dump(String prefix, PrintWriter pw) {
+ final String newPrefix = prefix + " ";
pw.println(prefix + "InsetsState");
+ pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame);
+ pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get());
+ pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
for (int i = 0; i < SIZE; i++) {
InsetsSource source = mSources[i];
if (source == null) continue;
- source.dump(prefix + " ", pw);
+ source.dump(newPrefix + " ", pw);
}
}
@@ -698,7 +745,8 @@
InsetsState state = (InsetsState) o;
if (!mDisplayFrame.equals(state.mDisplayFrame)
- || !mDisplayCutout.equals(state.mDisplayCutout)) {
+ || !mDisplayCutout.equals(state.mDisplayCutout)
+ || !mRoundedCorners.equals(state.mRoundedCorners)) {
return false;
}
for (int i = 0; i < SIZE; i++) {
@@ -722,7 +770,8 @@
@Override
public int hashCode() {
- return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources));
+ return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
+ mRoundedCorners);
}
public InsetsState(Parcel in) {
@@ -739,6 +788,7 @@
mDisplayFrame.writeToParcel(dest, flags);
mDisplayCutout.writeToParcel(dest, flags);
dest.writeParcelableArray(mSources, 0);
+ dest.writeTypedObject(mRoundedCorners, flags);
}
public static final @android.annotation.NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
@@ -756,6 +806,7 @@
mDisplayFrame.set(Rect.CREATOR.createFromParcel(in));
mDisplayCutout.set(DisplayCutout.ParcelableWrapper.CREATOR.createFromParcel(in));
mSources = in.readParcelableArray(null, InsetsSource.class);
+ mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
}
@Override
@@ -770,6 +821,7 @@
return "InsetsState: {"
+ "mDisplayFrame=" + mDisplayFrame
+ ", mDisplayCutout=" + mDisplayCutout
+ + ", mRoundedCorners=" + mRoundedCorners
+ ", mSources= { " + joiner
+ " }";
}
diff --git a/core/java/android/view/RoundedCorner.java b/core/java/android/view/RoundedCorner.java
new file mode 100644
index 0000000..cc7525b
--- /dev/null
+++ b/core/java/android/view/RoundedCorner.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a rounded corner of the display.
+ *
+ * <code>
+ * ________
+ * / ^
+ * / | Radius
+ * | v
+ * | X <- Center point
+ * |<----->
+ * Radius
+ * </code>
+ *
+ * <p>Note: The rounded corner formed by the radius and the center is an approximation.</p>
+ *
+ * <p>{@link RoundedCorner} is immutable.</p>
+ */
+public final class RoundedCorner implements Parcelable {
+
+ /**
+ * The rounded corner is at the top-left of the screen.
+ */
+ public static final int POSITION_TOP_LEFT = 0;
+ /**
+ * The rounded corner is at the top-right of the screen.
+ */
+ public static final int POSITION_TOP_RIGHT = 1;
+ /**
+ * The rounded corner is at the bottom-right of the screen.
+ */
+ public static final int POSITION_BOTTOM_RIGHT = 2;
+ /**
+ * The rounded corner is at the bottom-left of the screen.
+ */
+ public static final int POSITION_BOTTOM_LEFT = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "POSITION_" }, value = {
+ POSITION_TOP_LEFT,
+ POSITION_TOP_RIGHT,
+ POSITION_BOTTOM_RIGHT,
+ POSITION_BOTTOM_LEFT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Position {}
+
+ private final @Position int mPosition;
+ private final int mRadius;
+ @NonNull
+ private final Point mCenter;
+
+ /**
+ * Creates an empty {@link RoundedCorner} on the given position.
+ * @hide
+ */
+ @VisibleForTesting
+ public RoundedCorner(@Position int position) {
+ mPosition = position;
+ mRadius = 0;
+ mCenter = new Point(0, 0);
+ }
+
+ /**
+ * Creates a {@link RoundedCorner}.
+ *
+ * <p>Note that this is only useful for tests. For production code, developers should always
+ * use a {@link RoundedCorner} obtained from the system via
+ * {@link WindowInsets#getRoundedCorner} or {@link Display#getRoundedCorner}.</p>
+ *
+ * @param position the position of the rounded corner.
+ * @param radius the radius of the rounded corner.
+ * @param centerX the x of center point of the rounded corner.
+ * @param centerY the y of center point of the rounded corner.
+ *
+ */
+ public RoundedCorner(@Position int position, int radius, int centerX,
+ int centerY) {
+ mPosition = position;
+ mRadius = radius;
+ mCenter = new Point(centerX, centerY);
+ }
+
+ /**
+ * Creates a {@link RoundedCorner} from a passed in {@link RoundedCorner}.
+ *
+ * @hide
+ */
+ RoundedCorner(RoundedCorner rc) {
+ mPosition = rc.getPosition();
+ mRadius = rc.getRadius();
+ mCenter = new Point(rc.getCenter());
+ }
+
+ /**
+ * Get the position of this {@link RoundedCorner}.
+ *
+ * @see #POSITION_TOP_LEFT
+ * @see #POSITION_TOP_RIGHT
+ * @see #POSITION_BOTTOM_RIGHT
+ * @see #POSITION_BOTTOM_LEFT
+ */
+ public @Position int getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Returns the radius of a quarter circle approximation of this {@link RoundedCorner}.
+ *
+ * @return the rounded corner radius of this {@link RoundedCorner}. Returns 0 if there is no
+ * rounded corner.
+ */
+ public int getRadius() {
+ return mRadius;
+ }
+
+ /**
+ * Returns the circle center of a quarter circle approximation of this {@link RoundedCorner}.
+ *
+ * @return the center point of this {@link RoundedCorner} in the application's coordinate.
+ */
+ @NonNull
+ public Point getCenter() {
+ return new Point(mCenter);
+ }
+
+ /**
+ * Checks whether this {@link RoundedCorner} exists and is inside the application's bounds.
+ *
+ * @return {@code false} if there is a rounded corner and is contained in the application's
+ * bounds. Otherwise return {@code true}.
+ *
+ * @hide
+ */
+ public boolean isEmpty() {
+ return mRadius == 0 || mCenter.x == 0 || mCenter.y == 0;
+ }
+
+ private String getPositionString(@Position int position) {
+ switch (position) {
+ case POSITION_TOP_LEFT:
+ return "TopLeft";
+ case POSITION_TOP_RIGHT:
+ return "TopRight";
+ case POSITION_BOTTOM_RIGHT:
+ return "BottomRight";
+ case POSITION_BOTTOM_LEFT:
+ return "BottomLeft";
+ default:
+ return "Invalid";
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof RoundedCorner) {
+ RoundedCorner r = (RoundedCorner) o;
+ return mPosition == r.mPosition && mRadius == r.mRadius
+ && mCenter.equals(r.mCenter);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ result = 31 * result + mPosition;
+ result = 31 * result + mRadius;
+ result = 31 * result + mCenter.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "RoundedCorner{"
+ + "position=" + getPositionString(mPosition)
+ + ", radius=" + mRadius
+ + ", center=" + mCenter
+ + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mPosition);
+ out.writeInt(mRadius);
+ out.writeInt(mCenter.x);
+ out.writeInt(mCenter.y);
+ }
+
+ public static final @NonNull Creator<RoundedCorner> CREATOR = new Creator<RoundedCorner>() {
+ @Override
+ public RoundedCorner createFromParcel(Parcel in) {
+ return new RoundedCorner(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+ }
+
+ @Override
+ public RoundedCorner[] newArray(int size) {
+ return new RoundedCorner[size];
+ }
+ };
+}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/view/RoundedCorners.aidl
similarity index 74%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/view/RoundedCorners.aidl
index 284a4ba..0a901c0 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/view/RoundedCorners.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.attestation;
+package android.view;
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable RoundedCorners;
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
new file mode 100644
index 0000000..015e804
--- /dev/null
+++ b/core/java/android/view/RoundedCorners.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.view.RoundedCorner.Position;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A class to create & manage all the {@link RoundedCorner} on the display.
+ *
+ * @hide
+ */
+public class RoundedCorners implements Parcelable {
+
+ public static final RoundedCorners NO_ROUNDED_CORNERS = new RoundedCorners(
+ new RoundedCorner(POSITION_TOP_LEFT), new RoundedCorner(POSITION_TOP_RIGHT),
+ new RoundedCorner(POSITION_BOTTOM_RIGHT), new RoundedCorner(POSITION_BOTTOM_LEFT));
+
+ /**
+ * The number of possible positions at which rounded corners can be located.
+ */
+ public static final int ROUNDED_CORNER_POSITION_LENGTH = 4;
+
+ private static final Object CACHE_LOCK = new Object();
+
+ @GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayWidth;
+ @GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayHeight;
+ @GuardedBy("CACHE_LOCK")
+ private static Pair<Integer, Integer> sCachedRadii;
+ @GuardedBy("CACHE_LOCK")
+ private static RoundedCorners sCachedRoundedCorners;
+
+ @VisibleForTesting
+ public final RoundedCorner[] mRoundedCorners;
+
+ public RoundedCorners(RoundedCorner[] roundedCorners) {
+ mRoundedCorners = roundedCorners;
+ }
+
+ public RoundedCorners(RoundedCorner topLeft, RoundedCorner topRight, RoundedCorner bottomRight,
+ RoundedCorner bottomLeft) {
+ mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ mRoundedCorners[POSITION_TOP_LEFT] = topLeft;
+ mRoundedCorners[POSITION_TOP_RIGHT] = topRight;
+ mRoundedCorners[POSITION_BOTTOM_RIGHT] = bottomRight;
+ mRoundedCorners[POSITION_BOTTOM_LEFT] = bottomLeft;
+ }
+
+ public RoundedCorners(RoundedCorners roundedCorners) {
+ mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+ mRoundedCorners[i] = new RoundedCorner(roundedCorners.mRoundedCorners[i]);
+ }
+ }
+
+ /**
+ * Creates the rounded corners according to @android:dimen/rounded_corner_radius,
+ * @android:dimen/rounded_corner_radius_top and @android:dimen/rounded_corner_radius_bottom
+ */
+ public static RoundedCorners fromResources(
+ Resources res, int displayWidth, int displayHeight) {
+ return fromRadii(loadRoundedCornerRadii(res), displayWidth, displayHeight);
+ }
+
+ /**
+ * Creates the rounded corners from radius
+ */
+ @VisibleForTesting
+ public static RoundedCorners fromRadii(Pair<Integer, Integer> radii, int displayWidth,
+ int displayHeight) {
+ if (radii == null) {
+ return null;
+ }
+
+ synchronized (CACHE_LOCK) {
+ if (radii.equals(sCachedRadii) && sCachedDisplayWidth == displayWidth
+ && sCachedDisplayHeight == displayHeight) {
+ return sCachedRoundedCorners;
+ }
+ }
+
+ final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ final int topRadius = radii.first > 0 ? radii.first : 0;
+ final int bottomRadius = radii.second > 0 ? radii.second : 0;
+ for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
+ roundedCorners[i] = createRoundedCorner(
+ i,
+ i <= POSITION_TOP_RIGHT ? topRadius : bottomRadius,
+ displayWidth,
+ displayHeight);
+ }
+
+ final RoundedCorners result = new RoundedCorners(roundedCorners);
+ synchronized (CACHE_LOCK) {
+ sCachedDisplayWidth = displayWidth;
+ sCachedDisplayHeight = displayHeight;
+ sCachedRadii = radii;
+ sCachedRoundedCorners = result;
+ }
+ return result;
+ }
+
+ /**
+ * Loads the rounded corner radii from resources.
+ *
+ * @param res
+ * @return a Pair of radius. The first is the top rounded corner radius and second is the
+ * bottom corner radius.
+ */
+ @Nullable
+ private static Pair<Integer, Integer> loadRoundedCornerRadii(Resources res) {
+ final int radiusDefault = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
+ final int radiusTop = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top);
+ final int radiusBottom = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom);
+ if (radiusDefault == 0 && radiusTop == 0 && radiusBottom == 0) {
+ return null;
+ }
+ final Pair<Integer, Integer> radii = new Pair<>(
+ radiusTop > 0 ? radiusTop : radiusDefault,
+ radiusBottom > 0 ? radiusBottom : radiusDefault);
+ return radii;
+ }
+
+ /**
+ * Insets the reference frame of the rounded corners.
+ *
+ * @return a copy of this instance which has been inset
+ */
+ public RoundedCorners inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
+ final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
+ roundedCorners[i] = insetRoundedCorner(i, insetLeft, insetTop, insetRight, insetBottom);
+ }
+ return new RoundedCorners(roundedCorners);
+ }
+
+ private RoundedCorner insetRoundedCorner(@Position int position, int insetLeft,
+ int insetTop, int insetRight, int insetBottom) {
+ if (mRoundedCorners[position].isEmpty()) {
+ return new RoundedCorner(position);
+ }
+
+ final int radius = mRoundedCorners[position].getRadius();
+ final Point center = mRoundedCorners[position].getCenter();
+ boolean hasRoundedCorner;
+ switch (position) {
+ case POSITION_TOP_LEFT:
+ hasRoundedCorner = radius > insetTop || radius > insetLeft;
+ break;
+ case POSITION_TOP_RIGHT:
+ hasRoundedCorner = radius > insetTop || radius > insetRight;
+ break;
+ case POSITION_BOTTOM_RIGHT:
+ hasRoundedCorner = radius > insetBottom || radius > insetRight;
+ break;
+ case POSITION_BOTTOM_LEFT:
+ hasRoundedCorner = radius > insetBottom || radius > insetLeft;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "The position is not one of the RoundedCornerPosition =" + position);
+ }
+ return new RoundedCorner(
+ position, radius,
+ hasRoundedCorner ? center.x - insetLeft : 0,
+ hasRoundedCorner ? center.y - insetTop : 0);
+ }
+
+ /**
+ * Returns the {@link RoundedCorner} of the given position if there is one.
+ *
+ * @param position the position of the rounded corner on the display.
+ * @return the rounded corner of the given position. Returns {@code null} if
+ * {@link RoundedCorner#isEmpty()} is {@code true}.
+ */
+ @Nullable
+ public RoundedCorner getRoundedCorner(@Position int position) {
+ return mRoundedCorners[position].isEmpty()
+ ? null : new RoundedCorner(mRoundedCorners[position]);
+ }
+
+ /**
+ * Sets the rounded corner of given position.
+ *
+ * @param position the position of this rounded corner
+ * @param roundedCorner the rounded corner or null if there is none
+ */
+ public void setRoundedCorner(@Position int position, @Nullable RoundedCorner roundedCorner) {
+ mRoundedCorners[position] = roundedCorner == null
+ ? new RoundedCorner(position) : roundedCorner;
+ }
+
+ /**
+ * Returns an array of {@link RoundedCorner}s. Ordinal value of RoundedCornerPosition is used
+ * as an index of the array.
+ *
+ * @return an array of {@link RoundedCorner}s, one for each rounded corner area.
+ */
+ public RoundedCorner[] getAllRoundedCorners() {
+ RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+ roundedCorners[i] = new RoundedCorner(roundedCorners[i]);
+ }
+ return roundedCorners;
+ }
+
+ /**
+ * Returns a scaled RoundedCorners.
+ */
+ public RoundedCorners scale(float scale) {
+ if (scale == 1f) {
+ return this;
+ }
+
+ RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+ final RoundedCorner roundedCorner = mRoundedCorners[i];
+ roundedCorners[i] = new RoundedCorner(
+ i,
+ (int) (roundedCorner.getRadius() * scale),
+ (int) (roundedCorner.getCenter().x * scale),
+ (int) (roundedCorner.getCenter().y * scale));
+ }
+ return new RoundedCorners(roundedCorners);
+ }
+
+ /**
+ * Returns a rotated RoundedCorners.
+ */
+ public RoundedCorners rotate(@Surface.Rotation int rotation, int initialDisplayWidth,
+ int initialDisplayHeight) {
+ if (rotation == ROTATION_0) {
+ return this;
+ }
+ final boolean isSizeFlipped = rotation == ROTATION_90 || rotation == ROTATION_270;
+ RoundedCorner[] newCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ int newPosistion;
+ for (int i = 0; i < mRoundedCorners.length; i++) {
+ newPosistion = getRotatedIndex(i, rotation);
+ newCorners[newPosistion] = createRoundedCorner(
+ newPosistion,
+ mRoundedCorners[i].getRadius(),
+ isSizeFlipped ? initialDisplayHeight : initialDisplayWidth,
+ isSizeFlipped ? initialDisplayWidth : initialDisplayHeight);
+ }
+ return new RoundedCorners(newCorners);
+ }
+
+ private static RoundedCorner createRoundedCorner(@Position int position,
+ int radius, int displayWidth, int displayHeight) {
+ switch (position) {
+ case POSITION_TOP_LEFT:
+ return new RoundedCorner(
+ POSITION_TOP_LEFT,
+ radius,
+ radius > 0 ? radius : 0,
+ radius > 0 ? radius : 0);
+ case POSITION_TOP_RIGHT:
+ return new RoundedCorner(
+ POSITION_TOP_RIGHT,
+ radius,
+ radius > 0 ? displayWidth - radius : 0,
+ radius > 0 ? radius : 0);
+ case POSITION_BOTTOM_RIGHT:
+ return new RoundedCorner(
+ POSITION_BOTTOM_RIGHT,
+ radius,
+ radius > 0 ? displayWidth - radius : 0,
+ radius > 0 ? displayHeight - radius : 0);
+ case POSITION_BOTTOM_LEFT:
+ return new RoundedCorner(
+ POSITION_BOTTOM_LEFT,
+ radius,
+ radius > 0 ? radius : 0,
+ radius > 0 ? displayHeight - radius : 0);
+ default:
+ throw new IllegalArgumentException(
+ "The position is not one of the RoundedCornerPosition =" + position);
+ }
+ }
+
+ private static int getRotatedIndex(int position, int rotation) {
+ return (position - rotation + ROUNDED_CORNER_POSITION_LENGTH) % 4;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ for (RoundedCorner roundedCorner : mRoundedCorners) {
+ result = result * 31 + roundedCorner.hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof RoundedCorners) {
+ RoundedCorners r = (RoundedCorners) o;
+ return Arrays.deepEquals(mRoundedCorners, ((RoundedCorners) o).mRoundedCorners);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "RoundedCorners{" + Arrays.toString(mRoundedCorners) + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (equals(NO_ROUNDED_CORNERS)) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeTypedArray(mRoundedCorners, flags);
+ }
+ }
+
+ public static final @NonNull Creator<RoundedCorners> CREATOR = new Creator<RoundedCorners>() {
+ @Override
+ public RoundedCorners createFromParcel(Parcel in) {
+ int variant = in.readInt();
+ if (variant == 0) {
+ return NO_ROUNDED_CORNERS;
+ }
+ RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+ in.readTypedArray(roundedCorners, RoundedCorner.CREATOR);
+ return new RoundedCorners(roundedCorners);
+ }
+
+ @Override
+ public RoundedCorners[] newArray(int size) {
+ return new RoundedCorners[size];
+ }
+ };
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0a1a231..acd2507 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -750,18 +750,22 @@
private abstract static class CaptureArgs {
private final int mPixelFormat;
private final Rect mSourceCrop = new Rect();
- private final float mFrameScale;
+ private final float mFrameScaleX;
+ private final float mFrameScaleY;
private final boolean mCaptureSecureLayers;
private final boolean mAllowProtected;
private final long mUid;
+ private final boolean mGrayscale;
private CaptureArgs(Builder<? extends Builder<?>> builder) {
mPixelFormat = builder.mPixelFormat;
mSourceCrop.set(builder.mSourceCrop);
- mFrameScale = builder.mFrameScale;
+ mFrameScaleX = builder.mFrameScaleX;
+ mFrameScaleY = builder.mFrameScaleY;
mCaptureSecureLayers = builder.mCaptureSecureLayers;
mAllowProtected = builder.mAllowProtected;
mUid = builder.mUid;
+ mGrayscale = builder.mGrayscale;
}
/**
@@ -772,10 +776,12 @@
abstract static class Builder<T extends Builder<T>> {
private int mPixelFormat = PixelFormat.RGBA_8888;
private final Rect mSourceCrop = new Rect();
- private float mFrameScale = 1;
+ private float mFrameScaleX = 1;
+ private float mFrameScaleY = 1;
private boolean mCaptureSecureLayers;
private boolean mAllowProtected;
private long mUid = -1;
+ private boolean mGrayscale;
/**
* The desired pixel format of the returned buffer.
@@ -798,7 +804,18 @@
* The desired scale of the returned buffer. The raw screen will be scaled up/down.
*/
public T setFrameScale(float frameScale) {
- mFrameScale = frameScale;
+ mFrameScaleX = frameScale;
+ mFrameScaleY = frameScale;
+ return getThis();
+ }
+
+ /**
+ * The desired scale of the returned buffer, allowing separate values for x and y scale.
+ * The raw screen will be scaled up/down.
+ */
+ public T setFrameScale(float frameScaleX, float frameScaleY) {
+ mFrameScaleX = frameScaleX;
+ mFrameScaleY = frameScaleY;
return getThis();
}
@@ -834,6 +851,14 @@
}
/**
+ * Set whether the screenshot should use grayscale or not.
+ */
+ public T setGrayscale(boolean grayscale) {
+ mGrayscale = grayscale;
+ return getThis();
+ }
+
+ /**
* Each sub class should return itself to allow the builder to chain properly
*/
abstract T getThis();
@@ -929,7 +954,7 @@
/**
* The arguments class used to make layer capture requests.
*
- * @see #nativeCaptureLayers(LayerCaptureArgs)
+ * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
* @hide
*/
public static class LayerCaptureArgs extends CaptureArgs {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f603ef7..70ec2d4 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -877,6 +877,10 @@
synchronized (mSurfaceControlLock) {
mSurface.release();
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.destroy();
+ mBlastBufferQueue = null;
+ }
if (mRtHandlingPositionUpdates) {
mRtReleaseSurfaces = true;
@@ -901,10 +905,6 @@
transaction.remove(mBlastSurfaceControl);
mBlastSurfaceControl = null;
}
- if (mBlastBufferQueue != null) {
- mBlastBufferQueue.destroy();
- mBlastBufferQueue = null;
- }
}
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5c0e156..749c0df 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -41,6 +41,7 @@
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.annotation.UiThread;
@@ -9030,7 +9031,8 @@
* not be null or empty if a non-null listener is passed in.
* @param listener The listener to use. This can be null to reset to the default behavior.
*/
- public void setOnReceiveContentListener(@Nullable String[] mimeTypes,
+ public void setOnReceiveContentListener(
+ @SuppressLint("NullableCollection") @Nullable String[] mimeTypes,
@Nullable OnReceiveContentListener listener) {
if (listener != null) {
Preconditions.checkArgument(mimeTypes != null && mimeTypes.length > 0,
@@ -9106,6 +9108,7 @@
* @return The MIME types accepted by {@link #performReceiveContent} for this view (may
* include patterns such as "image/*").
*/
+ @SuppressLint("NullableCollection")
@Nullable
public String[] getOnReceiveContentMimeTypes() {
return mOnReceiveContentMimeTypes;
@@ -9803,20 +9806,6 @@
}
}
- private void notifyAttachForDrawables() {
- if (mBackground != null) mBackground.onAttached(this);
- if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
- mForegroundInfo.mDrawable.onAttached(this);
- }
- }
-
- private void notifyDetachForDrawables() {
- if (mBackground != null) mBackground.onDetached(this);
- if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
- mForegroundInfo.mDrawable.onDetached(this);
- }
- }
-
private void setNotifiedContentCaptureAppeared() {
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
@@ -10364,6 +10353,7 @@
for (int i = 0; i < childDrawIndex; i++) {
drawingOrderInParent += numViewsForAccessibility(preorderedList.get(i));
}
+ preorderedList.clear();
} else {
final int childIndex = parentGroup.indexOfChild(viewAtDrawingLevel);
final boolean customOrder = parentGroup.isChildrenDrawingOrderEnabled();
@@ -20668,7 +20658,6 @@
notifyEnterOrExitForAutoFillIfNeeded(true);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
- notifyAttachForDrawables();
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -20718,7 +20707,6 @@
notifyEnterOrExitForAutoFillIfNeeded(false);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
- notifyDetachForDrawables();
}
/**
@@ -21367,6 +21355,10 @@
int height = mBottom - mTop;
int layerType = getLayerType();
+ // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+ // instead of being "stateful" like other RenderNode properties
+ renderNode.clearStretch();
+
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
@@ -22793,6 +22785,11 @@
final Rect bounds = drawable.getBounds();
final int width = bounds.width();
final int height = bounds.height();
+
+ // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+ // instead of being "stateful" like other RenderNode properties
+ renderNode.clearStretch();
+
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
// Reverse left/top translation done by drawable canvas, which will
@@ -23847,7 +23844,6 @@
if (mBackground != null) {
if (isAttachedToWindow()) {
mBackground.setVisible(false, false);
- mBackground.onDetached(this);
}
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
@@ -23897,7 +23893,6 @@
background.setState(getDrawableState());
}
if (isAttachedToWindow()) {
- background.onAttached(this);
background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
}
@@ -24130,7 +24125,6 @@
if (mForegroundInfo.mDrawable != null) {
if (isAttachedToWindow()) {
mForegroundInfo.mDrawable.setVisible(false, false);
- mForegroundInfo.mDrawable.onDetached(this);
}
mForegroundInfo.mDrawable.setCallback(null);
unscheduleDrawable(mForegroundInfo.mDrawable);
@@ -24148,7 +24142,6 @@
}
applyForegroundTint();
if (isAttachedToWindow()) {
- foreground.onAttached(this);
foreground.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
}
// Set callback last, since the view may still be initializing.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 844fc26..18ef80c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -115,7 +115,6 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.RenderNode;
-import android.graphics.drawable.BackgroundBlurDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.hardware.display.DisplayManager;
@@ -139,6 +138,7 @@
import android.os.UserHandle;
import android.sysprop.DisplayProperties;
import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -158,6 +158,7 @@
import android.view.View.FocusDirection;
import android.view.View.MeasureSpec;
import android.view.Window.OnContentApplyWindowInsetsListener;
+import android.view.WindowInsets.Side.InsetsSide;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -187,6 +188,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
@@ -672,14 +674,6 @@
new BackgroundBlurDrawable.Aggregator(this);
/**
- * @return {@link BackgroundBlurDrawable.Aggregator} for this instance.
- */
- @NonNull
- public BackgroundBlurDrawable.Aggregator getBlurRegionAggregator() {
- return mBlurRegionAggregator;
- }
-
- /**
* @return {@link ImeFocusController} for this instance.
*/
@NonNull
@@ -934,6 +928,33 @@
}
}
+ // TODO(b/161810301): Make this private after window layout is moved to the client side.
+ public static void computeWindowBounds(WindowManager.LayoutParams attrs, InsetsState state,
+ Rect displayFrame, Rect outBounds) {
+ final @InsetsType int typesToFit = attrs.getFitInsetsTypes();
+ final @InsetsSide int sidesToFit = attrs.getFitInsetsSides();
+ final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
+ final Rect df = displayFrame;
+ Insets insets = Insets.of(0, 0, 0, 0);
+ for (int i = types.size() - 1; i >= 0; i--) {
+ final InsetsSource source = state.peekSource(types.valueAt(i));
+ if (source == null) {
+ continue;
+ }
+ insets = Insets.max(insets, source.calculateInsets(
+ df, attrs.isFitInsetsIgnoringVisibility()));
+ }
+ final int left = (sidesToFit & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
+ final int top = (sidesToFit & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
+ final int right = (sidesToFit & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
+ final int bottom = (sidesToFit & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
+ outBounds.set(df.left + left, df.top + top, df.right - right, df.bottom - bottom);
+ }
+
+ private Configuration getConfiguration() {
+ return mContext.getResources().getConfiguration();
+ }
+
/**
* We have one child
*/
@@ -1065,18 +1086,15 @@
controlInsetsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
- mInsetsController.getRequestedVisibility(), mTmpFrames.frame,
- inputChannel, mTempInsets, mTempControls);
+ mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
+ mTempControls);
if (mTranslator != null) {
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
}
- setFrame(mTmpFrames.frame);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
- inputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
@@ -1092,6 +1110,9 @@
mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);
+ computeWindowBounds(mWindowAttributes, mInsetsController.getState(),
+ getConfiguration().windowConfiguration.getBounds(), mTmpFrames.frame);
+ setFrame(mTmpFrames.frame);
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
@@ -1365,7 +1386,7 @@
}
private int getNightMode() {
- return mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
}
private void updateForceDarkMode() {
@@ -2341,7 +2362,7 @@
/* package */ WindowInsets getWindowInsets(boolean forceConstruct) {
if (mLastWindowInsets == null || forceConstruct) {
- final Configuration config = mContext.getResources().getConfiguration();
+ final Configuration config = getConfiguration();
mLastWindowInsets = mInsetsController.calculateInsets(
config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars,
mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
@@ -2477,7 +2498,7 @@
mFullRedrawNeeded = true;
mLayoutRequested = true;
- final Configuration config = mContext.getResources().getConfiguration();
+ final Configuration config = getConfiguration();
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
@@ -4770,7 +4791,7 @@
}
// TODO: Centralize this sanitization? Why do we let setting bad modes?
// Alternatively, can we just let HWUI figure it out? Do we need to care here?
- if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+ if (!getConfiguration().isScreenWideColorGamut()) {
colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
}
mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
@@ -10084,6 +10105,13 @@
}
}
+ /**
+ * Creates a background blur drawable for the backing {@link Surface}.
+ */
+ public BackgroundBlurDrawable createBackgroundBlurDrawable() {
+ return mBlurRegionAggregator.createBackgroundBlurDrawable(mContext);
+ }
+
@Override
public void onDescendantUnbufferedRequested() {
mUnbufferedInputSource = mView.mUnbufferedInputSource;
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index f5aa97a..8b3fb2e 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
@@ -377,7 +378,8 @@
* <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
* will be ignored when used for Assist.
*/
- public void setOnReceiveContentMimeTypes(@Nullable String[] mimeTypes) {}
+ public void setOnReceiveContentMimeTypes(
+ @SuppressLint("NullableCollection") @Nullable String[] mimeTypes) {}
/**
* Sets the {@link android.text.InputType} bits of this node.
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 7a5561c..41c38a1 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -82,6 +82,7 @@
@Nullable private Rect mTempRect;
private final boolean mIsRound;
@Nullable private final DisplayCutout mDisplayCutout;
+ @Nullable private final RoundedCorners mRoundedCorners;
/**
* In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -125,11 +126,12 @@
* @hide
* @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
*/
- public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect,
- boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
+ @Deprecated
+ public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound,
+ boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
- isRound, alwaysConsumeSystemBars, displayCutout, systemBars(),
+ isRound, alwaysConsumeSystemBars, displayCutout, null, systemBars(),
false /* compatIgnoreVisibility */);
}
@@ -150,7 +152,8 @@
boolean[] typeVisibilityMap,
boolean isRound,
boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
- @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
+ RoundedCorners roundedCorners, @InsetsType int compatInsetsTypes,
+ boolean compatIgnoreVisibility) {
mSystemWindowInsetsConsumed = typeInsetsMap == null;
mTypeInsetsMap = mSystemWindowInsetsConsumed
? new Insets[SIZE]
@@ -170,6 +173,8 @@
mDisplayCutoutConsumed = displayCutout == null;
mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
? null : displayCutout;
+
+ mRoundedCorners = roundedCorners;
}
/**
@@ -182,6 +187,7 @@
src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
src.mTypeVisibilityMap, src.mIsRound,
src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
+ src.mRoundedCorners,
src.mCompatInsetsTypes,
src.mCompatIgnoreVisibility);
}
@@ -235,7 +241,7 @@
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
- systemBars(), false /* compatIgnoreVisibility */);
+ null, systemBars(), false /* compatIgnoreVisibility */);
}
/**
@@ -466,7 +472,7 @@
public boolean hasInsets() {
return !getInsets(mTypeInsetsMap, all()).equals(Insets.NONE)
|| !getInsets(mTypeMaxInsetsMap, all()).equals(Insets.NONE)
- || mDisplayCutout != null;
+ || mDisplayCutout != null || mRoundedCorners != null;
}
/**
@@ -487,6 +493,23 @@
}
/**
+ * Returns the {@link RoundedCorner} of the given position if there is one.
+ *
+ * @param position the position of the rounded corner on the display. The value should be one of
+ * the following:
+ * {@link RoundedCorner#POSITION_TOP_LEFT},
+ * {@link RoundedCorner#POSITION_TOP_RIGHT},
+ * {@link RoundedCorner#POSITION_BOTTOM_RIGHT},
+ * {@link RoundedCorner#POSITION_BOTTOM_LEFT}.
+ * @return the rounded corner of the given position. Returns {@code null} if there is none or
+ * the rounded corner area is not inside the application's bounds.
+ */
+ @Nullable
+ public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
+ return mRoundedCorners == null ? null : mRoundedCorners.getRoundedCorner(position);
+ }
+
+ /**
* Returns a copy of this WindowInsets with the cutout fully consumed.
*
* @return A modified copy of this WindowInsets
@@ -501,7 +524,7 @@
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
- null /* displayCutout */,
+ null /* displayCutout */, mRoundedCorners,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -553,7 +576,7 @@
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
displayCutoutCopyConstructorArgument(this),
- mCompatInsetsTypes, mCompatIgnoreVisibility);
+ mRoundedCorners, mCompatInsetsTypes, mCompatIgnoreVisibility);
}
// TODO(b/119190588): replace @code with @link below
@@ -856,6 +879,8 @@
result.append(mDisplayCutout != null ? "cutout=" + mDisplayCutout : "");
result.append("\n ");
+ result.append(mRoundedCorners != null ? "roundedCorners=" + mRoundedCorners : "");
+ result.append("\n ");
result.append(isRound() ? "round" : "");
result.append("}");
return result.toString();
@@ -947,6 +972,9 @@
: mDisplayCutout == null
? DisplayCutout.NO_CUTOUT
: mDisplayCutout.inset(left, top, right, bottom),
+ mRoundedCorners == null
+ ? RoundedCorners.NO_ROUNDED_CORNERS
+ : mRoundedCorners.inset(left, top, right, bottom),
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -964,13 +992,14 @@
&& Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap)
&& Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap)
&& Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
- && Objects.equals(mDisplayCutout, that.mDisplayCutout);
+ && Objects.equals(mDisplayCutout, that.mDisplayCutout)
+ && Objects.equals(mRoundedCorners, that.mRoundedCorners);
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
- Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout,
+ Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed,
mDisplayCutoutConsumed);
}
@@ -1032,6 +1061,7 @@
private boolean mStableInsetsConsumed = true;
private DisplayCutout mDisplayCutout;
+ private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
private boolean mIsRound;
private boolean mAlwaysConsumeSystemBars;
@@ -1057,6 +1087,7 @@
mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed;
mStableInsetsConsumed = insets.mStableInsetsConsumed;
mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
+ mRoundedCorners = insets.mRoundedCorners;
mIsRound = insets.mIsRound;
mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
}
@@ -1262,6 +1293,29 @@
/** @hide */
@NonNull
+ public Builder setRoundedCorners(RoundedCorners roundedCorners) {
+ mRoundedCorners = roundedCorners != null
+ ? roundedCorners : RoundedCorners.NO_ROUNDED_CORNERS;
+ return this;
+ }
+
+ /**
+ * Sets the rounded corner of given position.
+ *
+ * @see #getRoundedCorner(int)
+ * @param position the position of this rounded corner
+ * @param roundedCorner the rounded corner or null if there is none
+ * @return itself
+ */
+ @NonNull
+ public Builder setRoundedCorner(@RoundedCorner.Position int position,
+ @Nullable RoundedCorner roundedCorner) {
+ mRoundedCorners.setRoundedCorner(position, roundedCorner);
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
public Builder setRound(boolean round) {
mIsRound = round;
return this;
@@ -1283,7 +1337,7 @@
public WindowInsets build() {
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
- mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout,
+ mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners,
systemBars(), false /* compatIgnoreVisibility */);
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fa471fa..9e87c95 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -83,6 +83,7 @@
import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -99,6 +100,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Build;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1509,13 +1511,7 @@
* Use {@link #dimAmount} to control the amount of dim. */
public static final int FLAG_DIM_BEHIND = 0x00000002;
- /** Window flag: enable blurring behind this window.
- * To set the amount of blur, use {@link #backgroundBlurRadius}
- *
- * @hide
- */
- @RequiresPermission(permission.USE_BACKGROUND_BLUR)
- @SystemApi
+ /** Window flag: enable blur behind for this window. */
public static final int FLAG_BLUR_BEHIND = 0x00000004;
/** Window flag: this window won't ever get key input focus, so the
@@ -2855,12 +2851,14 @@
/**
* The token of {@link android.app.WindowContext}. It is usually a
- * {@link android.app.WindowTokenClient} and is used for updating
- * {@link android.content.res.Resources} from {@link Configuration} propagated from the
- * server side.
+ * {@link android.app.WindowTokenClient} and is used for associating the params with an
+ * existing node in the WindowManager hierarchy and getting the corresponding
+ * {@link Configuration} and {@link android.content.res.Resources} values with updates
+ * propagated from the server side.
*
* @hide
*/
+ @Nullable
public IBinder mWindowContextToken = null;
/**
@@ -3233,15 +3231,16 @@
public boolean preferMinimalPostProcessing = false;
/**
- * When {@link FLAG_BLUR_BEHIND} is set, this is the amount of blur in pixels that this
- * window will use to blur behind itself.
- * The range is from 0, which means no blur, to 150.
+ * Specifies the amount of blur to be used to blur everything behind the window.
+ * The effect is similar to the dimAmount, but instead of dimming, the content behind
+ * will be blurred.
*
- * @hide
+ * The blur behind radius range starts at 0, which means no blur, and increases until 150
+ * for the densest blur.
+ *
+ * @see #FLAG_BLUR_BEHIND
*/
- @SystemApi
- @RequiresPermission(permission.USE_BACKGROUND_BLUR)
- public int backgroundBlurRadius = 0;
+ public int blurBehindRadius = 0;
/**
* The color mode requested by this window. The target display may
@@ -3547,6 +3546,37 @@
return userActivityTimeout;
}
+ /**
+ * Sets the {@link android.app.WindowContext} token.
+ *
+ * @see #getWindowContextToken()
+ *
+ * @hide
+ */
+ @TestApi
+ public final void setWindowContextToken(@NonNull IBinder token) {
+ mWindowContextToken = token;
+ }
+
+ /**
+ * Gets the {@link android.app.WindowContext} token.
+ *
+ * The token is usually a {@link android.app.WindowTokenClient} and is used for associating
+ * the params with an existing node in the WindowManager hierarchy and getting the
+ * corresponding {@link Configuration} and {@link android.content.res.Resources} values with
+ * updates propagated from the server side.
+ *
+ * @see android.app.WindowTokenClient
+ * @see Context#createWindowContext(Display, int, Bundle)
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public final IBinder getWindowContextToken() {
+ return mWindowContextToken;
+ }
+
public int describeContents() {
return 0;
}
@@ -3599,7 +3629,7 @@
out.writeInt(mFitInsetsSides);
out.writeBoolean(mFitInsetsIgnoringVisibility);
out.writeBoolean(preferMinimalPostProcessing);
- out.writeInt(backgroundBlurRadius);
+ out.writeInt(blurBehindRadius);
if (providesInsetsTypes != null) {
out.writeInt(providesInsetsTypes.length);
out.writeIntArray(providesInsetsTypes);
@@ -3668,7 +3698,7 @@
mFitInsetsSides = in.readInt();
mFitInsetsIgnoringVisibility = in.readBoolean();
preferMinimalPostProcessing = in.readBoolean();
- backgroundBlurRadius = in.readInt();
+ blurBehindRadius = in.readInt();
int insetsTypesLength = in.readInt();
if (insetsTypesLength > 0) {
providesInsetsTypes = new int[insetsTypesLength];
@@ -3913,8 +3943,8 @@
changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED;
}
- if (backgroundBlurRadius != o.backgroundBlurRadius) {
- backgroundBlurRadius = o.backgroundBlurRadius;
+ if (blurBehindRadius != o.blurBehindRadius) {
+ blurBehindRadius = o.blurBehindRadius;
changes |= BACKGROUND_BLUR_RADIUS_CHANGED;
}
@@ -4081,9 +4111,9 @@
sb.append(" preferMinimalPostProcessing=");
sb.append(preferMinimalPostProcessing);
}
- if (backgroundBlurRadius != 0) {
- sb.append(" backgroundBlurRadius=");
- sb.append(backgroundBlurRadius);
+ if (blurBehindRadius != 0) {
+ sb.append(" blurBehindRadius=");
+ sb.append(blurBehindRadius);
}
sb.append(System.lineSeparator());
sb.append(prefix).append(" fl=").append(
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index dd56c15..b85f1079 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -24,7 +24,7 @@
import android.graphics.Region;
import android.os.IBinder;
import android.os.RemoteException;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
import android.util.Log;
import android.util.MergedConfiguration;
import android.window.ClientWindowFrames;
@@ -135,7 +135,7 @@
*/
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsState requestedVisibility, Rect outFrame,
+ int viewVisibility, int displayId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
@@ -171,10 +171,10 @@
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
- Rect outFrame, InputChannel outInputChannel, InsetsState outInsetsState,
+ InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibility,
- outFrame, outInputChannel, outInsetsState, outActiveControls);
+ outInputChannel, outInsetsState, outActiveControls);
}
@Override
@@ -466,7 +466,7 @@
}
@Override
- public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+ public ScreenshotHash generateScreenshotHash(IWindow window, Rect boundsInWindow,
String hashAlgorithm) {
return null;
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 73962d7..10fd0e0 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -82,6 +82,7 @@
public static InlineSuggestionInfo newInlineSuggestionInfo(
@NonNull InlinePresentationSpec presentationSpec,
@NonNull @Source String source,
+ @SuppressLint("NullableCollection")
@Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) {
return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned);
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index a76d46d1..f3111bd 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -858,7 +858,7 @@
boolean reportFullscreenMode(boolean enabled);
/**
- * Have the editor perform spell checking around the current selection.
+ * Have the editor perform spell checking for the full content.
*
* <p>The editor can ignore this method call if it does not support spell checking.
*
diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS
index e6a04da..d7db7c7 100644
--- a/core/java/android/view/inputmethod/OWNERS
+++ b/core/java/android/view/inputmethod/OWNERS
@@ -2,3 +2,5 @@
set noparent
include /services/core/java/com/android/server/inputmethod/OWNERS
+
+per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 0a1aea3..5980cb6 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -187,7 +187,8 @@
* @return The spell checker session of the spell checker.
*/
@Nullable
- public SpellCheckerSession newSpellCheckerSession(@Nullable Bundle bundle,
+ public SpellCheckerSession newSpellCheckerSession(
+ @SuppressLint("NullableCollection") @Nullable Bundle bundle,
@SuppressLint("UseIcu") @Nullable Locale locale,
@NonNull SpellCheckerSessionListener listener,
@SuppressLint("ListenerLast") boolean referToSpellCheckerLanguageSettings,
@@ -277,6 +278,7 @@
* @return The list of currently enabled spell checkers.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public List<SpellCheckerInfo> getEnabledSpellCheckersList() {
final SpellCheckerInfo[] enabledSpellCheckers = getEnabledSpellCheckers();
return enabledSpellCheckers != null ? Arrays.asList(enabledSpellCheckers) : null;
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index fa46146..b49d3c0 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -101,15 +101,17 @@
}
break;
case STATE_UI_TRANSLATION_PAUSED:
- runForEachView((view) -> view.onPauseUiTranslation(), STATE_UI_TRANSLATION_PAUSED);
+ runForEachView(View::onPauseUiTranslation);
break;
case STATE_UI_TRANSLATION_RESUMED:
- runForEachView((view) -> view.onRestoreUiTranslation(),
- STATE_UI_TRANSLATION_PAUSED);
+ runForEachView(View::onRestoreUiTranslation);
break;
case STATE_UI_TRANSLATION_FINISHED:
destroyTranslators();
- runForEachView((view) -> view.onFinishUiTranslation(), STATE_UI_TRANSLATION_PAUSED);
+ runForEachView(View::onFinishUiTranslation);
+ synchronized (mLock) {
+ mViews.clear();
+ }
break;
default:
Log.w(TAG, "onAutoTranslationStateChange(): unknown state: " + state);
@@ -191,9 +193,6 @@
*/
private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
synchronized (mLock) {
- if (views == null || views.size() == 0) {
- throw new IllegalArgumentException("Invalid empty views: " + views);
- }
// Find Views collect the translation data
// TODO(b/178084101): try to optimize, e.g. to this in a single traversal
final int viewCounts = views.size();
@@ -223,22 +222,18 @@
}
}
- private void runForEachView(Consumer<View> action, @UiTranslationState int state) {
+ private void runForEachView(Consumer<View> action) {
synchronized (mLock) {
+ final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
mActivity.runOnUiThread(() -> {
- final int viewCounts = mViews.size();
+ final int viewCounts = views.size();
for (int i = 0; i < viewCounts; i++) {
- final View view = mViews.valueAt(i).get();
+ final View view = views.valueAt(i).get();
if (view == null) {
- Log.w(TAG, "The View for autofill id " + mViews.keyAt(i)
- + " may be gone for state " + stateToString(state));
continue;
}
action.accept(view);
}
- if (state == STATE_UI_TRANSLATION_FINISHED) {
- mViews.clear();
- }
});
}
}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 023d9ff2..20230e7 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -98,9 +98,17 @@
public abstract boolean acceptThirdPartyCookies(WebView webview);
/**
- * Sets a cookie for the given URL. Any existing cookie with the same host,
- * path and name will be replaced with the new cookie. The cookie being set
- * will be ignored if it is expired.
+ * Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
+ * host, path and name will be replaced with the new cookie. The cookie being set
+ * will be ignored if it is expired. To set multiple cookies, your application should invoke
+ * this method multiple times.
+ *
+ * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
+ * response header defined by
+ * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
+ * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
+ * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
+ * consult the RFC specification for a list of valid attributes.
*
* <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
* attribute, {@code url} must use the {@code "https://"} scheme.
@@ -112,13 +120,20 @@
public abstract void setCookie(String url, String value);
/**
- * Sets a cookie for the given URL. Any existing cookie with the same host,
- * path and name will be replaced with the new cookie. The cookie being set
- * will be ignored if it is expired.
- * <p>
- * This method is asynchronous.
- * If a {@link ValueCallback} is provided,
- * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
+ * Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
+ * host, path and name will be replaced with the new cookie. The cookie being set
+ * will be ignored if it is expired. To set multiple cookies, your application should invoke
+ * this method multiple times.
+ *
+ * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
+ * response header defined by
+ * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
+ * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
+ * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
+ * consult the RFC specification for a list of valid attributes.
+ *
+ * <p>This method is asynchronous. If a {@link ValueCallback} is provided,
+ * {@link ValueCallback#onReceiveValue} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether the cookie was set successfully.
* You can pass {@code null} as the callback if you don't need to know when the operation
@@ -137,7 +152,10 @@
callback);
/**
- * Gets the cookies for the given URL.
+ * Gets all the cookies for the given URL. This may return multiple key-value pairs if multiple
+ * cookies are associated with this URL, in which case each cookie will be delimited by {@code
+ * "; "} characters (semicolon followed by a space). Each key-value pair will be of the form
+ * {@code "key=value"}.
*
* @param url the URL for which the cookies are requested
* @return value the cookies as a string, using the format of the 'Cookie'
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 950dc73..c7eac6c 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -218,4 +218,15 @@
public String getDataDirectorySuffix() {
return WebViewFactory.getDataDirectorySuffix();
}
+
+ /**
+ * Returns an array of startup timestamps. For the specification of array
+ * see {@link WebViewFactory.Timestamp}.
+ * This method must be called on the same thread where the
+ * WebViewChromiumFactoryProvider#create method was invoked.
+ */
+ @NonNull
+ public long[] getTimestamps() {
+ return WebViewFactory.getTimestamps();
+ }
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b91e7d3..2e75834 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -35,6 +37,8 @@
import android.util.Log;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
/**
@@ -67,6 +71,33 @@
private static boolean sWebViewDisabled;
private static String sDataDirectorySuffix; // stored here so it can be set without loading WV
+ // Indices in sTimestamps array.
+ /** @hide */
+ @IntDef(value = {
+ WEBVIEW_LOAD_START, CREATE_CONTEXT_START, CREATE_CONTEXT_END,
+ ADD_ASSETS_START, ADD_ASSETS_END, GET_CLASS_LOADER_START, GET_CLASS_LOADER_END,
+ NATIVE_LOAD_START, NATIVE_LOAD_END,
+ PROVIDER_CLASS_FOR_NAME_START, PROVIDER_CLASS_FOR_NAME_END})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Timestamp {
+ }
+
+ public static final int WEBVIEW_LOAD_START = 0;
+ public static final int CREATE_CONTEXT_START = 1;
+ public static final int CREATE_CONTEXT_END = 2;
+ public static final int ADD_ASSETS_START = 3;
+ public static final int ADD_ASSETS_END = 4;
+ public static final int GET_CLASS_LOADER_START = 5;
+ public static final int GET_CLASS_LOADER_END = 6;
+ public static final int NATIVE_LOAD_START = 7;
+ public static final int NATIVE_LOAD_END = 8;
+ public static final int PROVIDER_CLASS_FOR_NAME_START = 9;
+ public static final int PROVIDER_CLASS_FOR_NAME_END = 10;
+ private static final int TIMESTAMPS_SIZE = 11;
+
+ // WebView startup timestamps. To access elements use {@link Timestamp}.
+ private static long[] sTimestamps = new long[TIMESTAMPS_SIZE];
+
// Error codes for loadWebViewNativeLibraryFromPackage
public static final int LIBLOAD_SUCCESS = 0;
public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
@@ -230,6 +261,7 @@
// us honest and minimize usage of WebView internals when binding the proxy.
if (sProviderInstance != null) return sProviderInstance;
+ sTimestamps[WEBVIEW_LOAD_START] = System.currentTimeMillis();
final int uid = android.os.Process.myUid();
if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
|| uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
@@ -369,6 +401,7 @@
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"initialApplication.createApplicationContext");
+ sTimestamps[CREATE_CONTEXT_START] = System.currentTimeMillis();
try {
// Construct an app context to load the Java code into the current app.
Context webViewContext = initialApplication.createApplicationContext(
@@ -377,6 +410,7 @@
sPackageInfo = newPackageInfo;
return webViewContext;
} finally {
+ sTimestamps[CREATE_CONTEXT_END] = System.currentTimeMillis();
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (RemoteException | PackageManager.NameNotFoundException e) {
@@ -402,20 +436,26 @@
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
+ sTimestamps[ADD_ASSETS_START] = System.currentTimeMillis();
for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
}
+ sTimestamps[ADD_ASSETS_END] = sTimestamps[GET_CLASS_LOADER_START] =
+ System.currentTimeMillis();
ClassLoader clazzLoader = webViewContext.getClassLoader();
-
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
+ sTimestamps[GET_CLASS_LOADER_END] = sTimestamps[NATIVE_LOAD_START] =
+ System.currentTimeMillis();
WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
getWebViewLibrary(sPackageInfo.applicationInfo));
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
-
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
+ sTimestamps[NATIVE_LOAD_END] = sTimestamps[PROVIDER_CLASS_FOR_NAME_START] =
+ System.currentTimeMillis();
try {
return getWebViewProviderClass(clazzLoader);
} finally {
+ sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = System.currentTimeMillis();
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (ClassNotFoundException e) {
@@ -477,4 +517,9 @@
return IWebViewUpdateService.Stub.asInterface(
ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
}
+
+ @NonNull
+ static long[] getTimestamps() {
+ return sTimestamps;
+ }
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index ffdb89d..93b2d8a 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -16,6 +16,8 @@
package android.widget;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -25,15 +27,22 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.text.format.DateUtils;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.RemotableViewMethod;
import android.view.View;
+import android.view.inspector.InspectableProperty;
import android.widget.RemoteViews.RemoteView;
import java.time.Clock;
+import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
+import java.util.Formatter;
+import java.util.Locale;
/**
* This widget display an analogic clock with two hands for hours and
@@ -42,25 +51,36 @@
* @attr ref android.R.styleable#AnalogClock_dial
* @attr ref android.R.styleable#AnalogClock_hand_hour
* @attr ref android.R.styleable#AnalogClock_hand_minute
+ * @attr ref android.R.styleable#AnalogClock_hand_second
+ * @attr ref android.R.styleable#AnalogClock_timeZone
* @deprecated This widget is no longer supported.
*/
@RemoteView
@Deprecated
public class AnalogClock extends View {
+ private static final String LOG_TAG = "AnalogClock";
+ /** How often the clock should refresh to make the seconds hand advance at ~15 FPS. */
+ private static final long SECONDS_TICK_FREQUENCY_MS = 1000 / 15;
+
private Clock mClock;
+ @Nullable
+ private ZoneId mTimeZone;
@UnsupportedAppUsage
private Drawable mHourHand;
@UnsupportedAppUsage
private Drawable mMinuteHand;
+ @Nullable
+ private Drawable mSecondHand;
@UnsupportedAppUsage
private Drawable mDial;
private int mDialWidth;
private int mDialHeight;
- private boolean mAttached;
+ private boolean mVisible;
+ private float mSeconds;
private float mMinutes;
private float mHour;
private boolean mChanged;
@@ -101,18 +121,111 @@
mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
}
- mClock = Clock.systemDefaultZone();
+ mSecondHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_second);
+
+ mTimeZone = toZoneId(a.getString(com.android.internal.R.styleable.AnalogClock_timeZone));
+ createClock();
mDialWidth = mDial.getIntrinsicWidth();
mDialHeight = mDial.getIntrinsicHeight();
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
+ /** Sets the dial of the clock to the specified Icon. */
+ @RemotableViewMethod
+ public void setDial(@NonNull Icon icon) {
+ mDial = icon.loadDrawable(getContext());
+ mDialWidth = mDial.getIntrinsicWidth();
+ mDialHeight = mDial.getIntrinsicHeight();
- if (!mAttached) {
- mAttached = true;
+ mChanged = true;
+ invalidate();
+ }
+
+ /** Sets the hour hand of the clock to the specified Icon. */
+ @RemotableViewMethod
+ public void setHourHand(@NonNull Icon icon) {
+ mHourHand = icon.loadDrawable(getContext());
+
+ mChanged = true;
+ invalidate();
+ }
+
+ /** Sets the minute hand of the clock to the specified Icon. */
+ @RemotableViewMethod
+ public void setMinuteHand(@NonNull Icon icon) {
+ mMinuteHand = icon.loadDrawable(getContext());
+
+ mChanged = true;
+ invalidate();
+ }
+
+ /**
+ * Sets the second hand of the clock to the specified Icon, or hides the second hand if it is
+ * null.
+ */
+ @RemotableViewMethod
+ public void setSecondHand(@Nullable Icon icon) {
+ mSecondHand = icon == null ? null : icon.loadDrawable(getContext());
+ mSecondsTick.run();
+
+ mChanged = true;
+ invalidate();
+ }
+
+ /**
+ * Indicates which time zone is currently used by this view.
+ *
+ * @return The ID of the current time zone or null if the default time zone,
+ * as set by the user, must be used
+ *
+ * @see java.util.TimeZone
+ * @see java.util.TimeZone#getAvailableIDs()
+ * @see #setTimeZone(String)
+ */
+ @InspectableProperty
+ @Nullable
+ public String getTimeZone() {
+ ZoneId zoneId = mTimeZone;
+ return zoneId == null ? null : zoneId.getId();
+ }
+
+ /**
+ * Sets the specified time zone to use in this clock. When the time zone
+ * is set through this method, system time zone changes (when the user
+ * sets the time zone in settings for instance) will be ignored.
+ *
+ * @param timeZone The desired time zone's ID as specified in {@link java.util.TimeZone}
+ * or null to user the time zone specified by the user
+ * (system time zone)
+ *
+ * @see #getTimeZone()
+ * @see java.util.TimeZone#getAvailableIDs()
+ * @see java.util.TimeZone#getTimeZone(String)
+ *
+ * @attr ref android.R.styleable#AnalogClock_timeZone
+ */
+ @RemotableViewMethod
+ public void setTimeZone(@Nullable String timeZone) {
+ mTimeZone = toZoneId(timeZone);
+
+ createClock();
+ onTimeChanged();
+ }
+
+ @Override
+ public void onVisibilityAggregated(boolean isVisible) {
+ super.onVisibilityAggregated(isVisible);
+
+ if (isVisible) {
+ onVisible();
+ } else {
+ onInvisible();
+ }
+ }
+
+ private void onVisible() {
+ if (!mVisible) {
+ mVisible = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
@@ -128,24 +241,25 @@
// user not the one the context is for.
getContext().registerReceiverAsUser(mIntentReceiver,
android.os.Process.myUserHandle(), filter, null, getHandler());
+
+ mSecondsTick.run();
}
// NOTE: It's safe to do these after registering the receiver since the receiver always runs
// in the main thread, therefore the receiver can't run before this method returns.
- // The time zone may have changed while the receiver wasn't registered, so update the Time
- mClock = Clock.systemDefaultZone();
+ // The time zone may have changed while the receiver wasn't registered, so update the clock.
+ createClock();
// Make sure we update to the current time
onTimeChanged();
}
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mAttached) {
+ private void onInvisible() {
+ if (mVisible) {
getContext().unregisterReceiver(mIntentReceiver);
- mAttached = false;
+ removeCallbacks(mSecondsTick);
+ mVisible = false;
}
}
@@ -237,6 +351,20 @@
minuteHand.draw(canvas);
canvas.restore();
+ final Drawable secondHand = mSecondHand;
+ if (secondHand != null) {
+ canvas.save();
+ canvas.rotate(mSeconds / 60.0f * 360.0f, x, y);
+
+ if (changed) {
+ w = secondHand.getIntrinsicWidth();
+ h = secondHand.getIntrinsicHeight();
+ secondHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
+ }
+ secondHand.draw(canvas);
+ canvas.restore();
+ }
+
if (scaled) {
canvas.restore();
}
@@ -250,6 +378,7 @@
int minute = localDateTime.getMinute();
int second = localDateTime.getSecond();
+ mSeconds = second + localDateTime.getNano() / 1_000_000_000f;
mMinutes = minute + second / 60.0f;
mHour = hour + mMinutes / 60.0f;
mChanged = true;
@@ -261,8 +390,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
- String tz = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
- mClock = Clock.system(ZoneId.of(tz));
+ createClock();
}
onTimeChanged();
@@ -271,9 +399,41 @@
}
};
+ private final Runnable mSecondsTick = new Runnable() {
+ @Override
+ public void run() {
+ if (!mVisible || mSecondHand == null) {
+ return;
+ }
+
+ onTimeChanged();
+
+ invalidate();
+
+ postDelayed(this, SECONDS_TICK_FREQUENCY_MS);
+ }
+ };
+
+ private void createClock() {
+ ZoneId zoneId = mTimeZone;
+ if (zoneId == null) {
+ mClock = Clock.systemDefaultZone();
+ } else {
+ mClock = Clock.system(zoneId);
+ }
+ }
+
private void updateContentDescription(long timeMillis) {
final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
- String contentDescription = DateUtils.formatDateTime(mContext, timeMillis, flags);
+ String contentDescription =
+ DateUtils.formatDateRange(
+ mContext,
+ new Formatter(new StringBuilder(50), Locale.getDefault()),
+ timeMillis /* startMillis */,
+ timeMillis /* endMillis */,
+ flags,
+ getTimeZone())
+ .toString();
setContentDescription(contentDescription);
}
@@ -284,4 +444,22 @@
Instant instant = Instant.ofEpochMilli(timeMillis);
return LocalDateTime.ofInstant(instant, zoneId);
}
+
+ /**
+ * Tries to parse a {@link ZoneId} from {@code timeZone}, returning null if it is null or there
+ * is an error parsing.
+ */
+ @Nullable
+ private static ZoneId toZoneId(@Nullable String timeZone) {
+ if (timeZone == null) {
+ return null;
+ }
+
+ try {
+ return ZoneId.of(timeZone);
+ } catch (DateTimeException e) {
+ Log.w(LOG_TAG, "Failed to parse time zone from " + timeZone, e);
+ return null;
+ }
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 26dd5e3..012352d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1705,15 +1705,23 @@
}
private void updateFloatingToolbarVisibility(MotionEvent event) {
- if (mTextActionMode != null) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_MOVE:
- hideFloatingToolbar(ActionMode.DEFAULT_HIDE_DURATION);
- break;
- case MotionEvent.ACTION_UP: // fall through
- case MotionEvent.ACTION_CANCEL:
+ if (mTextActionMode == null) {
+ return;
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ hideFloatingToolbar(ActionMode.DEFAULT_HIDE_DURATION);
+ break;
+ case MotionEvent.ACTION_UP: // fall through
+ case MotionEvent.ACTION_CANCEL:
+ final SelectionModifierCursorController selectionController =
+ getSelectionController();
+ final InsertionPointCursorController insertionController = getInsertionController();
+ if ((selectionController != null && selectionController.isCursorBeingModified())
+ || (insertionController != null
+ && insertionController.isCursorBeingModified())) {
showFloatingToolbar();
- }
+ }
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 8dafc5d..b47a0ac 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.ColorInt;
+import android.annotation.ColorRes;
import android.annotation.DimenRes;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
@@ -25,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
+import android.annotation.StringRes;
import android.annotation.StyleRes;
import android.app.Activity;
import android.app.ActivityOptions;
@@ -63,6 +65,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -180,6 +183,8 @@
private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21;
private static final int SET_INT_TAG_TAG = 22;
private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23;
+ private static final int RESOURCE_REFLECTION_ACTION_TAG = 24;
+ private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -980,6 +985,45 @@
return rect;
}
+ private static Class<?> getParameterType(int type) {
+ switch (type) {
+ case BaseReflectionAction.BOOLEAN:
+ return boolean.class;
+ case BaseReflectionAction.BYTE:
+ return byte.class;
+ case BaseReflectionAction.SHORT:
+ return short.class;
+ case BaseReflectionAction.INT:
+ return int.class;
+ case BaseReflectionAction.LONG:
+ return long.class;
+ case BaseReflectionAction.FLOAT:
+ return float.class;
+ case BaseReflectionAction.DOUBLE:
+ return double.class;
+ case BaseReflectionAction.CHAR:
+ return char.class;
+ case BaseReflectionAction.STRING:
+ return String.class;
+ case BaseReflectionAction.CHAR_SEQUENCE:
+ return CharSequence.class;
+ case BaseReflectionAction.URI:
+ return Uri.class;
+ case BaseReflectionAction.BITMAP:
+ return Bitmap.class;
+ case BaseReflectionAction.BUNDLE:
+ return Bundle.class;
+ case BaseReflectionAction.INTENT:
+ return Intent.class;
+ case BaseReflectionAction.COLOR_STATE_LIST:
+ return ColorStateList.class;
+ case BaseReflectionAction.ICON:
+ return Icon.class;
+ default:
+ return null;
+ }
+ }
+
private MethodHandle getMethod(View view, String methodName, Class<?> paramType,
boolean async) {
MethodArgs result;
@@ -1282,7 +1326,8 @@
@Override
public void apply(View root, ViewGroup rootParent,
OnClickHandler handler) throws ActionException {
- ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
+ ReflectionAction ra = new ReflectionAction(viewId, methodName,
+ BaseReflectionAction.BITMAP,
bitmap);
ra.apply(root, rootParent, handler);
}
@@ -1301,7 +1346,7 @@
/**
* Base class for the reflection actions.
*/
- private final class ReflectionAction extends Action {
+ private abstract class BaseReflectionAction extends Action {
static final int BOOLEAN = 1;
static final int BYTE = 2;
static final int SHORT = 3;
@@ -1324,17 +1369,14 @@
@UnsupportedAppUsage
String methodName;
int type;
- @UnsupportedAppUsage
- Object value;
- ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) {
+ BaseReflectionAction(@IdRes int viewId, String methodName, int type) {
this.viewId = viewId;
this.methodName = methodName;
this.type = type;
- this.value = value;
}
- ReflectionAction(Parcel in) {
+ BaseReflectionAction(Parcel in) {
this.viewId = in.readInt();
this.methodName = in.readString8();
this.type = in.readInt();
@@ -1343,7 +1385,125 @@
Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
+ " methodName=" + this.methodName + " type=" + this.type);
}
+ }
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(this.viewId);
+ out.writeString8(this.methodName);
+ out.writeInt(this.type);
+ }
+
+ /**
+ * Returns the value to use as parameter for the method.
+ *
+ * The view might be passed as {@code null} if the parameter value is requested outside of
+ * inflation. If the parameter cannot be determined at that time, the method should return
+ * {@code null} but not raise any exception.
+ */
+ @Nullable
+ protected abstract Object getParameterValue(@Nullable View view) throws ActionException;
+
+ @Override
+ public final void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final View view = root.findViewById(viewId);
+ if (view == null) return;
+
+ Class<?> param = getParameterType(this.type);
+ if (param == null) {
+ throw new ActionException("bad type: " + this.type);
+ }
+ Object value = getParameterValue(view);
+ try {
+ getMethod(view, this.methodName, param, false /* async */).invoke(view, value);
+ } catch (Throwable ex) {
+ throw new ActionException(ex);
+ }
+ }
+
+ @Override
+ public final Action initActionAsync(ViewTree root, ViewGroup rootParent,
+ OnClickHandler handler) {
+ final View view = root.findViewById(viewId);
+ if (view == null) return ACTION_NOOP;
+
+ Class<?> param = getParameterType(this.type);
+ if (param == null) {
+ throw new ActionException("bad type: " + this.type);
+ }
+
+ Object value = getParameterValue(view);
+ try {
+ MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
+
+ if (method != null) {
+ Runnable endAction = (Runnable) method.invoke(view, value);
+ if (endAction == null) {
+ return ACTION_NOOP;
+ }
+ // Special case view stub
+ if (endAction instanceof ViewStub.ViewReplaceRunnable) {
+ root.createTree();
+ // Replace child tree
+ root.findViewTreeById(viewId).replaceView(
+ ((ViewStub.ViewReplaceRunnable) endAction).view);
+ }
+ return new RunnableAction(endAction);
+ }
+ } catch (Throwable ex) {
+ throw new ActionException(ex);
+ }
+
+ return this;
+ }
+
+ public final int mergeBehavior() {
+ // smoothScrollBy is cumulative, everything else overwites.
+ if (methodName.equals("smoothScrollBy")) {
+ return MERGE_APPEND;
+ } else {
+ return MERGE_REPLACE;
+ }
+ }
+
+ @Override
+ public final String getUniqueKey() {
+ // Each type of reflection action corresponds to a setter, so each should be seen as
+ // unique from the standpoint of merging.
+ return super.getUniqueKey() + this.methodName + this.type;
+ }
+
+ @Override
+ public final boolean prefersAsyncApply() {
+ return this.type == URI || this.type == ICON;
+ }
+
+ @Override
+ public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ switch (this.type) {
+ case URI:
+ final Uri uri = (Uri) getParameterValue(null);
+ if (uri != null) visitor.accept(uri);
+ break;
+ case ICON:
+ final Icon icon = (Icon) getParameterValue(null);
+ if (icon != null) visitIconUri(icon, visitor);
+ break;
+ }
+ }
+ }
+
+ /** Class for the reflection actions. */
+ private final class ReflectionAction extends BaseReflectionAction {
+ @UnsupportedAppUsage
+ Object value;
+
+ ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) {
+ super(viewId, methodName, type);
+ this.value = value;
+ }
+
+ ReflectionAction(Parcel in) {
+ super(in);
// For some values that may have been null, we first check a flag to see if they were
// written to the parcel.
switch (this.type) {
@@ -1354,7 +1514,7 @@
this.value = in.readByte();
break;
case SHORT:
- this.value = (short)in.readInt();
+ this.value = (short) in.readInt();
break;
case INT:
this.value = in.readInt();
@@ -1369,7 +1529,7 @@
this.value = in.readDouble();
break;
case CHAR:
- this.value = (char)in.readInt();
+ this.value = (char) in.readInt();
break;
case STRING:
this.value = in.readString8();
@@ -1400,15 +1560,7 @@
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(this.viewId);
- out.writeString8(this.methodName);
- out.writeInt(this.type);
- //noinspection ConstantIfStatement
- if (false) {
- Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
- + " methodName=" + this.methodName + " type=" + this.type);
- }
-
+ super.writeToParcel(out, flags);
// For some values which are null, we record an integer flag to indicate whether
// we have written a valid value to the parcel.
switch (this.type) {
@@ -1434,13 +1586,13 @@
out.writeDouble((Double) this.value);
break;
case CHAR:
- out.writeInt((int)((Character)this.value).charValue());
+ out.writeInt((int) ((Character) this.value).charValue());
break;
case STRING:
- out.writeString8((String)this.value);
+ out.writeString8((String) this.value);
break;
case CHAR_SEQUENCE:
- TextUtils.writeToParcel((CharSequence)this.value, out, flags);
+ TextUtils.writeToParcel((CharSequence) this.value, out, flags);
break;
case BUNDLE:
out.writeBundle((Bundle) this.value);
@@ -1457,135 +1609,134 @@
}
}
- private Class<?> getParameterType() {
- switch (this.type) {
- case BOOLEAN:
- return boolean.class;
- case BYTE:
- return byte.class;
- case SHORT:
- return short.class;
- case INT:
- return int.class;
- case LONG:
- return long.class;
- case FLOAT:
- return float.class;
- case DOUBLE:
- return double.class;
- case CHAR:
- return char.class;
- case STRING:
- return String.class;
- case CHAR_SEQUENCE:
- return CharSequence.class;
- case URI:
- return Uri.class;
- case BITMAP:
- return Bitmap.class;
- case BUNDLE:
- return Bundle.class;
- case INTENT:
- return Intent.class;
- case COLOR_STATE_LIST:
- return ColorStateList.class;
- case ICON:
- return Icon.class;
- default:
- return null;
- }
- }
-
@Override
- public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
- final View view = root.findViewById(viewId);
- if (view == null) return;
-
- Class<?> param = getParameterType();
- if (param == null) {
- throw new ActionException("bad type: " + this.type);
- }
- try {
- getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value);
- } catch (Throwable ex) {
- throw new ActionException(ex);
- }
- }
-
- @Override
- public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
- final View view = root.findViewById(viewId);
- if (view == null) return ACTION_NOOP;
-
- Class<?> param = getParameterType();
- if (param == null) {
- throw new ActionException("bad type: " + this.type);
- }
-
- try {
- MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
-
- if (method != null) {
- Runnable endAction = (Runnable) method.invoke(view, this.value);
- if (endAction == null) {
- return ACTION_NOOP;
- } else {
- // Special case view stub
- if (endAction instanceof ViewStub.ViewReplaceRunnable) {
- root.createTree();
- // Replace child tree
- root.findViewTreeById(viewId).replaceView(
- ((ViewStub.ViewReplaceRunnable) endAction).view);
- }
- return new RunnableAction(endAction);
- }
- }
- } catch (Throwable ex) {
- throw new ActionException(ex);
- }
-
- return this;
- }
-
- public int mergeBehavior() {
- // smoothScrollBy is cumulative, everything else overwites.
- if (methodName.equals("smoothScrollBy")) {
- return MERGE_APPEND;
- } else {
- return MERGE_REPLACE;
- }
+ protected Object getParameterValue(View view) throws ActionException {
+ return this.value;
}
@Override
public int getActionTag() {
return REFLECTION_ACTION_TAG;
}
+ }
- @Override
- public String getUniqueKey() {
- // Each type of reflection action corresponds to a setter, so each should be seen as
- // unique from the standpoint of merging.
- return super.getUniqueKey() + this.methodName + this.type;
+ private final class ResourceReflectionAction extends BaseReflectionAction {
+
+ static final int DIMEN_RESOURCE = 1;
+ static final int COLOR_RESOURCE = 2;
+ static final int STRING_RESOURCE = 3;
+
+ private final int mResourceType;
+ private final int mResId;
+
+ ResourceReflectionAction(@IdRes int viewId, String methodName, int parameterType,
+ int resourceType, int resId) {
+ super(viewId, methodName, parameterType);
+ this.mResourceType = resourceType;
+ this.mResId = resId;
+ }
+
+ ResourceReflectionAction(Parcel in) {
+ super(in);
+ this.mResourceType = in.readInt();
+ this.mResId = in.readInt();
}
@Override
- public boolean prefersAsyncApply() {
- return this.type == URI || this.type == ICON;
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.mResourceType);
+ dest.writeInt(this.mResId);
}
@Override
- public void visitUris(@NonNull Consumer<Uri> visitor) {
- switch (this.type) {
- case URI:
- final Uri uri = (Uri) this.value;
- visitor.accept(uri);
- break;
- case ICON:
- final Icon icon = (Icon) this.value;
- visitIconUri(icon, visitor);
- break;
+ protected @NonNull Object getParameterValue(View view) throws ActionException {
+ Resources resources = view.getContext().getResources();
+ try {
+ switch (this.mResourceType) {
+ case DIMEN_RESOURCE:
+ if (this.type == BaseReflectionAction.INT) {
+ return resources.getDimensionPixelSize(this.mResId);
+ }
+ return resources.getDimension(this.mResId);
+ case COLOR_RESOURCE:
+ switch(this.type) {
+ case BaseReflectionAction.INT:
+ return view.getContext().getColor(this.mResId);
+ case BaseReflectionAction.COLOR_STATE_LIST:
+ return view.getContext().getColorStateList(this.mResId);
+ default:
+ throw new ActionException(
+ "color resources must be used as int or ColorStateList, "
+ + "not " + this.type);
+ }
+ case STRING_RESOURCE:
+ return resources.getText(this.mResId);
+ default:
+ throw new ActionException("unknown resource type: " + this.mResourceType);
+ }
+ } catch (Throwable t) {
+ throw new ActionException(t);
}
}
+
+ @Override
+ public int getActionTag() {
+ return RESOURCE_REFLECTION_ACTION_TAG;
+ }
+ }
+
+ private final class ComplexUnitDimensionReflectionAction extends BaseReflectionAction {
+
+ private final float mValue;
+ @ComplexDimensionUnit
+ private final int mUnit;
+
+ ComplexUnitDimensionReflectionAction(int viewId, String methodName, int parameterType,
+ float value, @ComplexDimensionUnit int unit) {
+ super(viewId, methodName, parameterType);
+ this.mValue = value;
+ this.mUnit = unit;
+ }
+
+ ComplexUnitDimensionReflectionAction(Parcel in) {
+ super(in);
+ this.mValue = in.readFloat();
+ this.mUnit = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeFloat(this.mValue);
+ dest.writeInt(this.mUnit);
+ }
+
+ @Override
+ protected Object getParameterValue(View view) throws ActionException {
+ DisplayMetrics dm = view.getContext().getResources().getDisplayMetrics();
+ try {
+ int data = TypedValue.createComplexDimension(this.mValue, this.mUnit);
+ switch (this.type) {
+ case ReflectionAction.INT:
+ return TypedValue.complexToDimensionPixelSize(data, dm);
+ case ReflectionAction.FLOAT:
+ return TypedValue.complexToDimension(data, dm);
+ default:
+ throw new ActionException(
+ "parameter type must be INT or FLOAT, not " + this.type);
+ }
+ } catch (ActionException ex) {
+ throw ex;
+ } catch (Throwable t) {
+ throw new ActionException(t);
+ }
+ }
+
+ @Override
+ public int getActionTag() {
+ return COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG;
+ }
}
/**
@@ -2611,6 +2762,10 @@
return new SetIntTagAction(parcel);
case REMOVE_FROM_PARENT_ACTION_TAG:
return new RemoveFromParentAction(parcel);
+ case RESOURCE_REFLECTION_ACTION_TAG:
+ return new ResourceReflectionAction(parcel);
+ case COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG:
+ return new ComplexUnitDimensionReflectionAction(parcel);
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -3113,7 +3268,7 @@
*/
public void setProgressTintList(@IdRes int viewId, ColorStateList tint) {
addAction(new ReflectionAction(viewId, "setProgressTintList",
- ReflectionAction.COLOR_STATE_LIST, tint));
+ BaseReflectionAction.COLOR_STATE_LIST, tint));
}
/**
@@ -3125,7 +3280,7 @@
*/
public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) {
addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
- ReflectionAction.COLOR_STATE_LIST, tint));
+ BaseReflectionAction.COLOR_STATE_LIST, tint));
}
/**
@@ -3137,7 +3292,7 @@
*/
public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) {
addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
- ReflectionAction.COLOR_STATE_LIST, tint));
+ BaseReflectionAction.COLOR_STATE_LIST, tint));
}
/**
@@ -3159,8 +3314,8 @@
* @param colors the text colors to set
*/
public void setTextColor(@IdRes int viewId, ColorStateList colors) {
- addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST,
- colors));
+ addAction(new ReflectionAction(viewId, "setTextColor",
+ BaseReflectionAction.COLOR_STATE_LIST, colors));
}
/**
@@ -3353,7 +3508,7 @@
* @param value The value to pass to the method.
*/
public void setBoolean(@IdRes int viewId, String methodName, boolean value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BOOLEAN, value));
}
/**
@@ -3364,7 +3519,7 @@
* @param value The value to pass to the method.
*/
public void setByte(@IdRes int viewId, String methodName, byte value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BYTE, value));
}
/**
@@ -3375,7 +3530,7 @@
* @param value The value to pass to the method.
*/
public void setShort(@IdRes int viewId, String methodName, short value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.SHORT, value));
}
/**
@@ -3386,10 +3541,59 @@
* @param value The value to pass to the method.
*/
public void setInt(@IdRes int viewId, String methodName, int value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INT, value));
}
/**
+ * Call a method taking one int, a size in pixels, on a view in the layout for this
+ * RemoteViews.
+ *
+ * The dimension will be resolved from the resources at the time of inflation.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param dimenResource The resource to resolve and pass as argument to the method.
+ */
+ public void setIntDimen(@IdRes int viewId, @NonNull String methodName,
+ @DimenRes int dimenResource) {
+ addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT,
+ ResourceReflectionAction.DIMEN_RESOURCE, dimenResource));
+ }
+
+ /**
+ * Call a method taking one int, a size in pixels, on a view in the layout for this
+ * RemoteViews.
+ *
+ * The dimension will be resolved from the specified dimension at the time of inflation.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param value The value of the dimension.
+ * @param unit The unit in which the value is specified.
+ */
+ public void setIntDimen(@IdRes int viewId, @NonNull String methodName,
+ float value, @ComplexDimensionUnit int unit) {
+ addAction(new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.INT,
+ value, unit));
+ }
+
+ /**
+ * Call a method taking one int, a color, on a view in the layout for this RemoteViews.
+ *
+ * The ColorStateList will be resolved from the resources at the time of inflation.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param colorResource The resource to resolve and pass as argument to the method.
+ */
+ public void setColor(@IdRes int viewId, @NonNull String methodName,
+ @ColorRes int colorResource) {
+ addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT,
+ ResourceReflectionAction.COLOR_RESOURCE, colorResource));
+ }
+
+
+ /**
* Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
@@ -3399,10 +3603,25 @@
* @hide
*/
public void setColorStateList(@IdRes int viewId, String methodName, ColorStateList value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.COLOR_STATE_LIST,
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.COLOR_STATE_LIST,
value));
}
+ /**
+ * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
+ *
+ * The ColorStateList will be resolved from the resources at the time of inflation.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param colorResource The resource to resolve and pass as argument to the method.
+ */
+ public void setColorStateList(@IdRes int viewId, @NonNull String methodName,
+ @ColorRes int colorResource) {
+ addAction(new ResourceReflectionAction(viewId, methodName,
+ BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE,
+ colorResource));
+ }
/**
* Call a method taking one long on a view in the layout for this RemoteViews.
@@ -3412,7 +3631,7 @@
* @param value The value to pass to the method.
*/
public void setLong(@IdRes int viewId, String methodName, long value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.LONG, value));
}
/**
@@ -3423,7 +3642,41 @@
* @param value The value to pass to the method.
*/
public void setFloat(@IdRes int viewId, String methodName, float value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, value));
+ }
+
+ /**
+ * Call a method taking one float, a size in pixels, on a view in the layout for this
+ * RemoteViews.
+ *
+ * The dimension will be resolved from the resources at the time of inflation.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param dimenResource The resource to resolve and pass as argument to the method.
+ */
+ public void setFloatDimen(@IdRes int viewId, @NonNull String methodName,
+ @DimenRes int dimenResource) {
+ addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT,
+ ResourceReflectionAction.DIMEN_RESOURCE, dimenResource));
+ }
+
+ /**
+ * Call a method taking one float, a size in pixels, on a view in the layout for this
+ * RemoteViews.
+ *
+ * The dimension will be resolved from the specified dimension at the time of inflation.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param value The value of the dimension.
+ * @param unit The unit in which the value is specified.
+ */
+ public void setFloatDimen(@IdRes int viewId, @NonNull String methodName,
+ float value, @ComplexDimensionUnit int unit) {
+ addAction(
+ new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.FLOAT,
+ value, unit));
}
/**
@@ -3434,7 +3687,7 @@
* @param value The value to pass to the method.
*/
public void setDouble(@IdRes int viewId, String methodName, double value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.DOUBLE, value));
}
/**
@@ -3445,7 +3698,7 @@
* @param value The value to pass to the method.
*/
public void setChar(@IdRes int viewId, String methodName, char value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR, value));
}
/**
@@ -3456,7 +3709,7 @@
* @param value The value to pass to the method.
*/
public void setString(@IdRes int viewId, String methodName, String value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.STRING, value));
}
/**
@@ -3467,7 +3720,24 @@
* @param value The value to pass to the method.
*/
public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE,
+ value));
+ }
+
+ /**
+ * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
+ *
+ * The CharSequence will be resolved from the resources at the time of inflation.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param stringResource The resource to resolve and pass as argument to the method.
+ */
+ public void setCharSequence(@IdRes int viewId, @NonNull String methodName,
+ @StringRes int stringResource) {
+ addAction(
+ new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE,
+ ResourceReflectionAction.STRING_RESOURCE, stringResource));
}
/**
@@ -3485,7 +3755,7 @@
value.checkFileUriExposed("RemoteViews.setUri()");
}
}
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.URI, value));
}
/**
@@ -3510,7 +3780,7 @@
* @param value The value to pass to the method.
*/
public void setBundle(@IdRes int viewId, String methodName, Bundle value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BUNDLE, value));
}
/**
@@ -3521,7 +3791,7 @@
* @param value The {@link android.content.Intent} to pass the method.
*/
public void setIntent(@IdRes int viewId, String methodName, Intent value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INTENT, value));
}
/**
@@ -3532,7 +3802,7 @@
* @param value The {@link android.graphics.drawable.Icon} to pass the method.
*/
public void setIcon(@IdRes int viewId, String methodName, Icon value) {
- addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value));
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.ICON, value));
}
/**
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 97d98fd..794b642 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -220,29 +220,26 @@
}
void onPerformSpellCheck() {
- final int selectionStart = mTextView.getSelectionStart();
- final int selectionEnd = mTextView.getSelectionEnd();
- final int selectionRangeStart;
- final int selectionRangeEnd;
- if (selectionStart < selectionEnd) {
- selectionRangeStart = selectionStart;
- selectionRangeEnd = selectionEnd;
- } else {
- selectionRangeStart = selectionEnd;
- selectionRangeEnd = selectionStart;
- }
- // Expand the range so that it (hopefully) includes the current sentence.
- final int start = Math.max(0, selectionRangeStart - MIN_SENTENCE_LENGTH);
- final int end = Math.min(mTextView.length(), selectionRangeEnd + MIN_SENTENCE_LENGTH);
+ // Triggers full content spell check.
+ final int start = 0;
+ final int end = mTextView.length();
if (DBG) {
Log.d(TAG, "performSpellCheckAroundSelection: " + start + ", " + end);
}
- spellCheck(start, end);
+ spellCheck(start, end, /* forceCheckWhenEditingWord= */ true);
}
public void spellCheck(int start, int end) {
+ spellCheck(start, end, /* forceCheckWhenEditingWord= */ false);
+ }
+
+ /**
+ * Requests to do spell check for text in the range (start, end).
+ */
+ public void spellCheck(int start, int end, boolean forceCheckWhenEditingWord) {
if (DBG) {
- Log.d(TAG, "Start spell-checking: " + start + ", " + end);
+ Log.d(TAG, "Start spell-checking: " + start + ", " + end + ", "
+ + forceCheckWhenEditingWord);
}
final Locale locale = mTextView.getSpellCheckerLocale();
final boolean isSessionActive = isSessionActive();
@@ -267,7 +264,7 @@
for (int i = 0; i < length; i++) {
final SpellParser spellParser = mSpellParsers[i];
if (spellParser.isFinished()) {
- spellParser.parse(start, end);
+ spellParser.parse(start, end, forceCheckWhenEditingWord);
return;
}
}
@@ -282,10 +279,14 @@
SpellParser spellParser = new SpellParser();
mSpellParsers[length] = spellParser;
- spellParser.parse(start, end);
+ spellParser.parse(start, end, forceCheckWhenEditingWord);
}
private void spellCheck() {
+ spellCheck(/* forceCheckWhenEditingWord= */ false);
+ }
+
+ private void spellCheck(boolean forceCheckWhenEditingWord) {
if (mSpellCheckerSession == null) return;
Editable editable = (Editable) mTextView.getText();
@@ -295,6 +296,12 @@
TextInfo[] textInfos = new TextInfo[mLength];
int textInfosCount = 0;
+ if (DBG) {
+ Log.d(TAG, "forceCheckWhenEditingWord=" + forceCheckWhenEditingWord
+ + ", mLength=" + mLength + ", cookie = " + mCookie
+ + ", sel start = " + selectionStart + ", sel end = " + selectionEnd);
+ }
+
for (int i = 0; i < mLength; i++) {
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) continue;
@@ -319,7 +326,7 @@
} else {
isEditing = selectionEnd < start || selectionStart > end;
}
- if (start >= 0 && end > start && isEditing) {
+ if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
spellCheckSpan.setSpellCheckInProgress(true);
final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
textInfos[textInfosCount++] = textInfo;
@@ -546,7 +553,11 @@
private class SpellParser {
private Object mRange = new Object();
- public void parse(int start, int end) {
+ // Forces to do spell checker even user is editing the word.
+ private boolean mForceCheckWhenEditingWord;
+
+ public void parse(int start, int end, boolean forceCheckWhenEditingWord) {
+ mForceCheckWhenEditingWord = forceCheckWhenEditingWord;
final int max = mTextView.length();
final int parseEnd;
if (end > max) {
@@ -567,6 +578,7 @@
public void stop() {
removeRangeSpan((Editable) mTextView.getText());
+ mForceCheckWhenEditingWord = false;
}
private void setRangeSpan(Editable editable, int start, int end) {
@@ -617,7 +629,7 @@
if (DBG) {
Log.i(TAG, "No more spell check.");
}
- removeRangeSpan(editable);
+ stop();
return;
}
@@ -649,7 +661,7 @@
if (DBG) {
Log.i(TAG, "Incorrect range span.");
}
- removeRangeSpan(editable);
+ stop();
return;
}
do {
@@ -778,7 +790,7 @@
removeRangeSpan(editable);
}
- spellCheck();
+ spellCheck(mForceCheckWhenEditingWord);
}
private <T> void removeSpansAt(Editable editable, int offset, T[] spans) {
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index f29eb39..cdb4762 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.os.IBinder;
@@ -151,6 +152,7 @@
/** Gets direct child tasks (ordered from top-to-bottom) */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@Nullable
+ @SuppressLint("NullableCollection")
public List<ActivityManager.RunningTaskInfo> getChildTasks(
@NonNull WindowContainerToken parent, @NonNull int[] activityTypes) {
try {
@@ -163,6 +165,7 @@
/** Gets all root tasks on a display (ordered from top-to-bottom) */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@Nullable
+ @SuppressLint("NullableCollection")
public List<ActivityManager.RunningTaskInfo> getRootTasks(
int displayId, @NonNull int[] activityTypes) {
try {
diff --git a/core/java/com/android/internal/annotations/CompositeRWLock.java b/core/java/com/android/internal/annotations/CompositeRWLock.java
new file mode 100644
index 0000000..b6ddfc4
--- /dev/null
+++ b/core/java/com/android/internal/annotations/CompositeRWLock.java
@@ -0,0 +1,47 @@
+/*
+ * 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.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies a list of locks which are required for read/write operations on a data field.
+ *
+ * <p>
+ * To annotate methods accessing the data field with the annotation {@link CompositeRWLock},
+ * use {@link GuardedBy#value} to annotate method w/ write and/or read access to the data field,
+ * use {@link GuardedBy#anyOf} to annotate method w/ read only access to the data field.
+ * </p>
+ *
+ * <p>
+ * When its {@link #value()} consists of multiple locks:
+ * <ul>
+ * <li>To write to the protected data, acquire <b>all</b> of the locks
+ * in the order of the appearance in the {@link #value}.</li>
+ * <li>To read from the protected data, acquire any of the locks in the {@link #value}.</li>
+ * </ul>
+ * </p>
+ */
+@Target({FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface CompositeRWLock {
+ String[] value() default {};
+}
diff --git a/core/java/com/android/internal/annotations/GuardedBy.java b/core/java/com/android/internal/annotations/GuardedBy.java
index 0e63214..c05c4ab 100644
--- a/core/java/com/android/internal/annotations/GuardedBy.java
+++ b/core/java/com/android/internal/annotations/GuardedBy.java
@@ -16,7 +16,9 @@
package com.android.internal.annotations;
-import java.lang.annotation.ElementType;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -25,8 +27,32 @@
* Annotation type used to mark a method or field that can only be accessed when
* holding the referenced locks.
*/
-@Target({ ElementType.FIELD, ElementType.METHOD })
+@Target({FIELD, METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface GuardedBy {
- String[] value();
+ /**
+ * Specifies a list of locks to be held in order to access the field/method
+ * annotated with this; when used in conjunction with the {@link CompositeRWLock}, locks
+ * should be acquired in the order of the appearance in the {@link #value} here.
+ *
+ * <p>
+ * If specified, {@link #anyOf()} must be null.
+ * </p>
+ *
+ * @see CompositeRWLock
+ */
+ String[] value() default {};
+
+ /**
+ * Specifies a list of locks where at least one of them must be held in order to access
+ * the field/method annotated with this; it should be <em>only</em> used in the conjunction
+ * with the {@link CompositeRWLock}.
+ *
+ * <p>
+ * If specified, {@link #allOf()} must be null.
+ * </p>
+ *
+ * @see CompositeRWLock
+ */
+ String[] anyOf() default {};
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 55f8c40..c1952c7 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -52,7 +52,7 @@
// Remaining methods are only used in Java.
- BatteryUsageStats getBatteryUsageStats(in BatteryUsageStatsQuery query);
+ List<BatteryUsageStats> getBatteryUsageStats(in List<BatteryUsageStatsQuery> queries);
@UnsupportedAppUsage
byte[] getStatistics();
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 99692d0..7ade05c 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -5,3 +5,4 @@
per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS
per-file IVoice* = file:/core/java/android/service/voice/OWNERS
per-file *Hotword* = file:/core/java/android/service/voice/OWNERS
+per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 13358daf..ee98878 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -62,6 +62,11 @@
*/
public static final String ENABLE_NAS_FEEDBACK = "enable_nas_feedback";
+ /**
+ * Whether the Notification Assistant can label a notification not a conversation
+ */
+ public static final String ENABLE_NAS_NOT_CONVERSATION = "enable_nas_not_conversation";
+
// Flags related to screenshot intelligence
/**
@@ -420,6 +425,12 @@
public static final String PIP_STASHING = "pip_stashing";
/**
+ * (float) The threshold velocity to cause PiP to be stashed when flinging from one edge to the
+ * other.
+ */
+ public static final String PIP_STASH_MINIMUM_VELOCITY_THRESHOLD = "pip_velocity_threshold";
+
+ /**
* (float) Bottom height in DP for Back Gesture.
*/
public static final String BACK_GESTURE_BOTTOM_HEIGHT = "back_gesture_bottom_height";
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index af666d8..9a44c05 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -16,6 +16,7 @@
package com.android.internal.content;
+import android.annotation.NonNull;
import android.app.Activity;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -234,7 +235,7 @@
/**
* Called when an existing package is updated or its disabled state changes.
*/
- public void onPackageModified(String packageName) {
+ public void onPackageModified(@NonNull String packageName) {
}
public boolean didSomePackagesChange() {
diff --git a/graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
similarity index 78%
rename from graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java
rename to core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
index 7e75c5b..96dac56 100644
--- a/graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java
+++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package android.graphics.drawable;
+package com.android.internal.graphics.drawable;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
+import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -31,71 +30,59 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
-import android.view.View;
import android.view.ViewRootImpl;
+import com.android.internal.R;
+
/**
* A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
* to SurfaceFlinger.
- *
- * @hide
*/
-@SystemApi
public final class BackgroundBlurDrawable extends Drawable {
+
private static final String TAG = BackgroundBlurDrawable.class.getSimpleName();
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final Aggregator mAggregator;
private final RenderNode mRenderNode;
private final Paint mPaint = new Paint();
private final Path mRectPath = new Path();
private final float[] mTmpRadii = new float[8];
private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion();
- private Aggregator mAggregator;
-
// This will be called from a thread pool.
private final RenderNode.PositionUpdateListener mPositionUpdateListener =
new RenderNode.PositionUpdateListener() {
@Override
public void positionChanged(long frameNumber, int left, int top, int right,
int bottom) {
- if (mAggregator == null) {
+ synchronized (mAggregator) {
mBlurRegion.rect.set(left, top, right, bottom);
- } else {
- synchronized (mAggregator) {
- mBlurRegion.rect.set(left, top, right, bottom);
- mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
- }
+ mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
}
}
@Override
public void positionLost(long frameNumber) {
- if (mAggregator == null) {
+ synchronized (mAggregator) {
mBlurRegion.rect.setEmpty();
- } else {
- synchronized (mAggregator) {
- mBlurRegion.rect.setEmpty();
- mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
- }
+ mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
}
}
};
- @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR)
- public BackgroundBlurDrawable() {
+ private BackgroundBlurDrawable(Aggregator aggregator) {
+ mAggregator = aggregator;
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
mPaint.setColor(Color.TRANSPARENT);
mRenderNode = new RenderNode("BackgroundBlurDrawable");
mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
}
- /**
- * @hide
- */
@Override
public void draw(@NonNull Canvas canvas) {
if (mRectPath.isEmpty() || !isVisible() || getAlpha() == 0) {
@@ -113,9 +100,6 @@
mPaint.setColor(color);
}
- /**
- * @hide
- */
@Override
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
@@ -125,9 +109,6 @@
return changed;
}
- /**
- * @hide
- */
@Override
public void setAlpha(int alpha) {
mBlurRegion.alpha = alpha / 255f;
@@ -158,12 +139,12 @@
*/
public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL,
float cornerRadiusBR) {
- maybeRunSynchronized(() -> {
+ synchronized (mAggregator) {
mBlurRegion.cornerRadiusTL = cornerRadiusTL;
mBlurRegion.cornerRadiusTR = cornerRadiusTR;
mBlurRegion.cornerRadiusBL = cornerRadiusBL;
mBlurRegion.cornerRadiusBR = cornerRadiusBR;
- });
+ }
updatePath();
invalidateSelf();
}
@@ -176,13 +157,12 @@
}
private void updatePath() {
- maybeRunSynchronized(() -> {
+ synchronized (mAggregator) {
mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL;
mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR;
mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL;
mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR;
- });
-
+ }
mRectPath.reset();
if (getAlpha() == 0 || !isVisible()) {
return;
@@ -192,62 +172,19 @@
Path.Direction.CW);
}
- /**
- * @hide
- */
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
throw new IllegalArgumentException("not implemented");
}
- /**
- * @hide
- */
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
/**
- * @hide
- */
- @Override
- public void onAttached(@NonNull View v) {
- super.onAttached(v);
- mAggregator = v.getViewRootImpl().getBlurRegionAggregator();
- }
-
- /**
- * @hide
- */
- @Override
- public void onDetached(@NonNull View v) {
- super.onDetached(v);
- mAggregator = null;
- }
-
- /**
- * The Aggregator is called from the RenderThread to aggregate all blur regions and send them
- * to SurfaceFlinger. Since the BackgroundBlurDrawable could be updated at any time from the
- * main thread, we need to synchronize the two threads. The BackgroundBlurDrawable may be
- * instantiated before the ViewRootImpl is created, i.e. before the Aggregator is created.
- * In that case, updates are not synchronized.
- */
- private void maybeRunSynchronized(Runnable r) {
- if (mAggregator == null) {
- r.run();
- } else {
- synchronized (mAggregator) {
- r.run();
- }
- }
- }
-
- /**
* Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a
* message when it's time to propagate them.
- *
- * @hide
*/
public static final class Aggregator {
@@ -262,6 +199,16 @@
}
/**
+ * Creates a blur region with default radius.
+ */
+ public BackgroundBlurDrawable createBackgroundBlurDrawable(Context context) {
+ BackgroundBlurDrawable drawable = new BackgroundBlurDrawable(this);
+ drawable.setBlurRadius(context.getResources().getDimensionPixelSize(
+ R.dimen.default_background_blur_radius));
+ return drawable;
+ }
+
+ /**
* Called from RenderThread only, already locked.
* @param drawable
* @param blurRegion
diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
index cafe0de..dfcc914 100644
--- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
+++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
@@ -17,8 +17,8 @@
package com.android.internal.graphics.fonts;
import android.os.ParcelFileDescriptor;
+import android.graphics.fonts.FontUpdateRequest;
import android.text.FontConfig;
-import android.graphics.fonts.SystemFontState;
/**
* System private interface for talking with
@@ -28,5 +28,5 @@
interface IFontManager {
FontConfig getFontConfig();
- int updateFont(in ParcelFileDescriptor fd, in byte[] signature, int baseVersion);
+ int updateFont(int baseVersion, in FontUpdateRequest request);
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e0f9554..3d896c8 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -59,6 +59,7 @@
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
import com.android.internal.jank.FrameTracker.ViewRootWrapper;
+import com.android.internal.util.PerfettoTrigger;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/core/java/com/android/internal/jank/PerfettoTrigger.java b/core/java/com/android/internal/jank/PerfettoTrigger.java
deleted file mode 100644
index 643d24a..0000000
--- a/core/java/com/android/internal/jank/PerfettoTrigger.java
+++ /dev/null
@@ -1,73 +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.
- */
-
-//TODO (165884885): Make PerfettoTrigger more generic and move it to another package.
-package com.android.internal.jank;
-
-import android.annotation.NonNull;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-/**
- * A trigger implementation with perfetto backend.
- * @hide
- */
-public class PerfettoTrigger {
- private static final String TAG = PerfettoTrigger.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
-
- /**
- * @param triggerName The name of the trigger. Must match the value defined in the AOT
- * Perfetto config.
- */
- public static void trigger(String triggerName) {
- try {
- ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
- if (DEBUG) {
- StringBuilder sb = new StringBuilder();
- for (String arg : pb.command()) {
- sb.append(arg).append(" ");
- }
- Log.d(TAG, "Triggering " + sb.toString());
- }
- Process process = pb.start();
- if (DEBUG) {
- readConsoleOutput(process);
- }
- } catch (IOException | InterruptedException e) {
- Log.w(TAG, "Failed to trigger " + triggerName, e);
- }
- }
-
- private static void readConsoleOutput(@NonNull Process process)
- throws IOException, InterruptedException {
- process.waitFor();
- try (BufferedReader errReader =
- new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
- StringBuilder errLine = new StringBuilder();
- String line;
- while ((line = errReader.readLine()) != null) {
- errLine.append(line).append("\n");
- }
- errLine.append(", code=").append(process.exitValue());
- Log.d(TAG, "err message=" + errLine.toString());
- }
- }
-}
diff --git a/core/java/com/android/internal/listeners/ListenerTransport.java b/core/java/com/android/internal/listeners/ListenerTransport.java
index 9d6210e..1a6870c 100644
--- a/core/java/com/android/internal/listeners/ListenerTransport.java
+++ b/core/java/com/android/internal/listeners/ListenerTransport.java
@@ -16,54 +16,43 @@
package com.android.internal.listeners;
-
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.internal.util.Preconditions;
-
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
- * A listener registration object which holds data associated with a listener, such the executor
- * the listener should run on.
+ * A listener transport object which can run listener operations on an executor.
*
* @param <TListener> listener type
*/
-public class ListenerTransport<TListener> {
-
- private final Executor mExecutor;
-
- private volatile @Nullable TListener mListener;
-
- protected ListenerTransport(@NonNull Executor executor, @NonNull TListener listener) {
- Preconditions.checkArgument(executor != null, "invalid null executor");
- Preconditions.checkArgument(listener != null, "invalid null listener/callback");
- mExecutor = executor;
- mListener = listener;
- }
+public interface ListenerTransport<TListener> {
/**
- * Prevents any listener invocations that happen-after this call.
+ * Should return a valid listener until {@link #unregister()} is invoked, and must return
+ * null after that. Recommended (but not required) that this is implemented via a volatile
+ * variable.
*/
- public final void unregister() {
- mListener = null;
- }
+ @Nullable TListener getListener();
+
+ /**
+ * Must be implemented so that {@link #getListener()} returns null after this is invoked.
+ */
+ void unregister();
/**
* Executes the given operation for the listener.
*/
- public final void execute(@NonNull Consumer<TListener> operation) {
+ default void execute(Executor executor, Consumer<TListener> operation) {
Objects.requireNonNull(operation);
- if (mListener == null) {
+ if (getListener() == null) {
return;
}
- mExecutor.execute(() -> {
- TListener listener = mListener;
+ executor.execute(() -> {
+ TListener listener = getListener();
if (listener == null) {
return;
}
@@ -71,15 +60,4 @@
operation.accept(listener);
});
}
-
- @Override
- public final boolean equals(Object obj) {
- // intentionally bound to reference equality so removal works as expected
- return this == obj;
- }
-
- @Override
- public final int hashCode() {
- return super.hashCode();
- }
}
diff --git a/core/java/com/android/internal/listeners/ListenerTransportManager.java b/core/java/com/android/internal/listeners/ListenerTransportManager.java
new file mode 100644
index 0000000..0d5d1b7b
--- /dev/null
+++ b/core/java/com/android/internal/listeners/ListenerTransportManager.java
@@ -0,0 +1,97 @@
+/*
+ * 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.listeners;
+
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * A listener transport manager which handles mappings between the client facing listener and system
+ * server facing transport. Supports transports which may be removed either from the client side or
+ * from the system server side without leaking memory.
+ *
+ * @param <TTransport>> transport type
+ */
+public abstract class ListenerTransportManager<TTransport extends ListenerTransport<?>> {
+
+ @GuardedBy("mRegistrations")
+ private final Map<Object, WeakReference<TTransport>> mRegistrations;
+
+ protected ListenerTransportManager() {
+ // using weakhashmap means that the transport may be GCed if the server drops its reference,
+ // and thus the listener may be GCed as well if the client drops that reference. if the
+ // server will never drop a reference without warning (ie, transport removal may only be
+ // initiated from the client side), then arraymap or similar may be used without fear of
+ // memory leaks.
+ mRegistrations = new WeakHashMap<>();
+ }
+
+ /**
+ * Adds a new transport with the given listener key.
+ */
+ public final void addListener(Object key, TTransport transport) {
+ try {
+ synchronized (mRegistrations) {
+ // ordering of operations is important so that if an error occurs at any point we
+ // are left in a reasonable state
+ registerTransport(transport);
+ WeakReference<TTransport> oldTransportRef = mRegistrations.put(key,
+ new WeakReference<>(transport));
+ if (oldTransportRef != null) {
+ TTransport oldTransport = oldTransportRef.get();
+ if (oldTransport != null) {
+ oldTransport.unregister();
+ unregisterTransport(oldTransport);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the transport with the given listener key.
+ */
+ public final void removeListener(Object key) {
+ try {
+ synchronized (mRegistrations) {
+ // ordering of operations is important so that if an error occurs at any point we
+ // are left in a reasonable state
+ WeakReference<TTransport> transportRef = mRegistrations.remove(key);
+ if (transportRef != null) {
+ TTransport transport = transportRef.get();
+ if (transport != null) {
+ transport.unregister();
+ unregisterTransport(transport);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ protected abstract void registerTransport(TTransport transport) throws RemoteException;
+
+ protected abstract void unregisterTransport(TTransport transport) throws RemoteException;
+}
diff --git a/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java b/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java
deleted file mode 100644
index fc1d69f..0000000
--- a/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java
+++ /dev/null
@@ -1,258 +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.listeners;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Build;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.IndentingPrintWriter;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-
-import java.io.FileDescriptor;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * A listener multiplexer designed for use by client-side code. This class ensures that listeners
- * are never invoked while a lock is held. This class is only useful for multiplexing listeners -
- * if all client listeners can be combined into a single server request, and all server results will
- * be delivered to all clients.
- *
- * By default, the multiplexer will replace requests on the server simply by registering the new
- * request and trusting the server to know this is replacing the old request. If the server needs to
- * have the old request unregistered first, subclasses should override
- * {@link #reregisterWithServer(Object, Object)}.
- *
- * @param <TRequest> listener request type, may be Void
- * @param <TListener> listener type
- */
-public abstract class ListenerTransportMultiplexer<TRequest, TListener> {
-
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> mRegistrations =
- new ArrayMap<>();
-
- @GuardedBy("mLock")
- private boolean mServiceRegistered = false;
-
- @GuardedBy("mLock")
- private TRequest mCurrentRequest;
-
- /**
- * Should be implemented to register the given merged request with the server.
- *
- * @see #reregisterWithServer(Object, Object)
- */
- protected abstract void registerWithServer(TRequest mergedRequest) throws RemoteException;
-
- /**
- * Invoked when the server already has a request registered, and it is being replaced with a new
- * request. The default implementation simply registers the new request, trusting the server to
- * overwrite the old request.
- */
- protected void reregisterWithServer(TRequest oldMergedRequest, TRequest mergedRequest)
- throws RemoteException {
- registerWithServer(mergedRequest);
- }
-
- /**
- * Should be implemented to unregister from the server.
- */
- protected abstract void unregisterWithServer() throws RemoteException;
-
- /**
- * Called in order to generate a merged request from the given requests. The list of requests
- * will never be empty.
- */
- protected @Nullable TRequest mergeRequests(Collection<TRequest> requests) {
- if (Build.IS_DEBUGGABLE) {
- for (TRequest request : requests) {
- // if using non-null requests then implementations must override this method
- Preconditions.checkState(request == null);
- }
- }
-
- return null;
- }
-
- /**
- * Adds a new listener with no request, using the listener as the key.
- */
- public void addListener(@NonNull TListener listener, @NonNull Executor executor) {
- addListener(listener, null, listener, executor);
- }
-
- /**
- * Adds a new listener with the given request, using the listener as the key.
- */
- public void addListener(@Nullable TRequest request, @NonNull TListener listener,
- @NonNull Executor executor) {
- addListener(listener, request, listener, executor);
- }
-
- /**
- * Adds a new listener with the given request using a custom key.
- */
- public void addListener(@NonNull Object key, @Nullable TRequest request,
- @NonNull TListener listener, @NonNull Executor executor) {
- Objects.requireNonNull(key);
- RequestListenerTransport<TRequest, TListener> registration =
- new RequestListenerTransport<>(request, executor, listener);
-
- synchronized (mLock) {
- ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> newRegistrations =
- new ArrayMap<>(mRegistrations.size() + 1);
- newRegistrations.putAll(mRegistrations);
- RequestListenerTransport<TRequest, TListener> old = newRegistrations.put(key,
- registration);
- mRegistrations = newRegistrations;
-
- if (old != null) {
- old.unregister();
- }
-
- updateService();
- }
- }
-
- /**
- * Removes the listener with the given key.
- */
- public void removeListener(@NonNull Object key) {
- Objects.requireNonNull(key);
-
- synchronized (mLock) {
- if (!mRegistrations.containsKey(key)) {
- return;
- }
-
- ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> newRegistrations =
- new ArrayMap<>(mRegistrations);
- RequestListenerTransport<TRequest, TListener> old = newRegistrations.remove(key);
- mRegistrations = newRegistrations;
-
- if (old != null) {
- old.unregister();
- updateService();
- }
- }
- }
-
- private void updateService() {
- synchronized (mLock) {
- if (mRegistrations.isEmpty()) {
- mCurrentRequest = null;
- if (mServiceRegistered) {
- try {
- mServiceRegistered = false;
- unregisterWithServer();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return;
- }
-
- ArrayList<TRequest> requests = new ArrayList<>(mRegistrations.size());
- for (int i = 0; i < mRegistrations.size(); i++) {
- requests.add(mRegistrations.valueAt(i).getRequest());
- }
-
- TRequest merged = mergeRequests(requests);
- if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
- TRequest old = mCurrentRequest;
- mCurrentRequest = null;
- try {
- if (mServiceRegistered) {
- // if a remote exception is thrown the service should not be registered
- mServiceRegistered = false;
- reregisterWithServer(old, merged);
- } else {
- registerWithServer(merged);
- }
- mCurrentRequest = merged;
- mServiceRegistered = true;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
- }
-
- protected final void deliverToListeners(Consumer<TListener> operation) {
- ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> registrations;
- synchronized (mLock) {
- registrations = mRegistrations;
- }
-
- try {
- for (int i = 0; i < registrations.size(); i++) {
- registrations.valueAt(i).execute(operation);
- }
- } finally {
- onOperationFinished(operation);
- }
- }
-
- /**
- * Invoked when an operation is finished. This method will always be called once for every call
- * to {@link #deliverToListeners(Consumer)}, regardless of whether the operation encountered any
- * error or failed to execute in any way for any listeners.
- */
- protected void onOperationFinished(@NonNull Consumer<TListener> operation) {}
-
- /**
- * Dumps debug information.
- */
- public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
- ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> registrations;
- synchronized (mLock) {
- registrations = mRegistrations;
-
- ipw.print("service: ");
- if (mServiceRegistered) {
- if (mCurrentRequest == null) {
- ipw.print("request registered");
- } else {
- ipw.print("request registered - " + mCurrentRequest);
- }
- } else {
- ipw.print("unregistered");
- }
- ipw.println();
- }
-
- if (!registrations.isEmpty()) {
- ipw.println("listeners:");
-
- ipw.increaseIndent();
- for (int i = 0; i < registrations.size(); i++) {
- ipw.print(registrations.valueAt(i));
- }
- ipw.decreaseIndent();
- }
- }
-}
diff --git a/core/java/com/android/internal/listeners/RequestListenerTransport.java b/core/java/com/android/internal/listeners/RequestListenerTransport.java
deleted file mode 100644
index 178de06..0000000
--- a/core/java/com/android/internal/listeners/RequestListenerTransport.java
+++ /dev/null
@@ -1,45 +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.listeners;
-
-import android.annotation.Nullable;
-
-import java.util.concurrent.Executor;
-
-/**
- * A listener transport with an associated request.
- *
- * @param <TRequest> request type
- * @param <TListener> listener type
- */
-public class RequestListenerTransport<TRequest, TListener> extends ListenerTransport<TListener> {
-
- private final @Nullable TRequest mRequest;
-
- protected RequestListenerTransport(@Nullable TRequest request, Executor executor,
- TListener listener) {
- super(executor, listener);
- mRequest = request;
- }
-
- /**
- * Returns the request associated with this transport.
- */
- public final @Nullable TRequest getRequest() {
- return mRequest;
- }
-}
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 6609ebe..8fe17fb 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -54,6 +54,7 @@
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah)
.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs);
}
+ // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
}
/**
@@ -66,7 +67,8 @@
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
- final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ final double powerMah = getMeasuredOrEstimatedPower(
+ batteryStats.getScreenDozeEnergy(), durationMs);
if (powerMah > 0) {
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0);
bs.usagePowerMah = powerMah;
@@ -79,4 +81,12 @@
private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000;
}
+
+ private double getMeasuredOrEstimatedPower(long measuredEnergyUJ, long durationMs) {
+ if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+ return mAhToUJ(measuredEnergyUJ);
+ } else {
+ return mPowerEstimator.calculatePower(durationMs);
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index c8805dd..af61f91 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -35,6 +35,8 @@
/**
* Smeared power from screen usage.
* We split the screen usage power and smear them among apps, based on activity time.
+ * The actual screen usage power may be measured or estimated, affecting the granularity and
+ * accuracy of the smearing, but the smearing algorithm is essentially the same.
*/
public double screenPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index fcf8bb4..aa5015a 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -37,7 +37,6 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
-import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 2b034b0..1f7a7aa 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -21,8 +21,6 @@
import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
-import static com.android.internal.power.MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -107,7 +105,7 @@
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import com.android.internal.power.MeasuredEnergyStats;
-import com.android.internal.power.MeasuredEnergyStats.EnergyBucket;
+import com.android.internal.power.MeasuredEnergyStats.StandardEnergyBucket;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -173,7 +171,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 191 + (USE_OLD_HISTORY ? 1000 : 0);
+ static final int VERSION = 193 + (USE_OLD_HISTORY ? 1000 : 0);
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -362,7 +360,6 @@
public interface PlatformIdleStateCallback {
public void fillLowPowerStats(RpmStats rpmStats);
- public String getPlatformLowPowerStats();
public String getSubsystemLowPowerStats();
}
@@ -3482,11 +3479,6 @@
}
if (computeStepDetails) {
if (mPlatformIdleStateCallback != null) {
- mCurHistoryStepDetails.statPlatformIdleState =
- mPlatformIdleStateCallback.getPlatformLowPowerStats();
- if (DEBUG) Slog.i(TAG, "WRITE PlatformIdleState:" +
- mCurHistoryStepDetails.statPlatformIdleState);
-
mCurHistoryStepDetails.statSubsystemPowerState =
mPlatformIdleStateCallback.getSubsystemLowPowerStats();
if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
@@ -7167,8 +7159,8 @@
if (mGlobalMeasuredEnergyStats == null) {
return ENERGY_DATA_UNAVAILABLE;
}
- return mGlobalMeasuredEnergyStats.getAccumulatedBucketEnergy(
- MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+ return mGlobalMeasuredEnergyStats
+ .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
}
@Override
@@ -7176,8 +7168,8 @@
if (mGlobalMeasuredEnergyStats == null) {
return ENERGY_DATA_UNAVAILABLE;
}
- return mGlobalMeasuredEnergyStats.getAccumulatedBucketEnergy(
- MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+ return mGlobalMeasuredEnergyStats
+ .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
}
@Override public long getStartClockTime() {
@@ -7941,27 +7933,27 @@
return mUidMeasuredEnergyStats;
}
- /** Adds the given energy to the given energy bucket for this uid. */
- private void addEnergyToEnergyBucketLocked(long energyDeltaUJ,
- @MeasuredEnergyStats.EnergyBucket int energyBucket, boolean accumulate) {
+ /** Adds the given energy to the given standard energy bucket for this uid. */
+ private void addEnergyToStandardBucketLocked(long energyDeltaUJ,
+ @StandardEnergyBucket int energyBucket, boolean accumulate) {
getOrCreateMeasuredEnergyStatsLocked()
- .updateBucket(energyBucket, energyDeltaUJ, accumulate);
+ .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
}
/**
- * Returns the energy used by this uid for an energy bucket of interest.
- * @param bucket energy bucket of interest
+ * Returns the energy used by this uid for a standard energy bucket of interest.
+ * @param bucket standard energy bucket of interest
* @return energy (in microjoules) used by this uid for this energy bucket
*/
- public long getMeasuredEnergyMicroJoules(@MeasuredEnergyStats.EnergyBucket int bucket) {
+ public long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) {
if (mBsi.mGlobalMeasuredEnergyStats == null
- || !mBsi.mGlobalMeasuredEnergyStats.isEnergyBucketSupported(bucket)) {
+ || !mBsi.mGlobalMeasuredEnergyStats.isStandardBucketSupported(bucket)) {
return ENERGY_DATA_UNAVAILABLE;
}
if (mUidMeasuredEnergyStats == null) {
return 0L; // It is supported, but was never filled, so it must be 0
}
- return mUidMeasuredEnergyStats.getAccumulatedBucketEnergy(bucket);
+ return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
}
/**
@@ -8633,11 +8625,7 @@
@Override
public long getScreenOnEnergy() {
- if (mUidMeasuredEnergyStats == null) {
- return ENERGY_DATA_UNAVAILABLE;
- }
- return mUidMeasuredEnergyStats.getAccumulatedBucketEnergy(
- MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+ return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
}
void initNetworkActivityLocked() {
@@ -12406,7 +12394,7 @@
return;
}
- final @EnergyBucket int energyBucket =
+ final @StandardEnergyBucket int energyBucket =
MeasuredEnergyStats.getDisplayEnergyBucket(mScreenStateAtLastEnergyMeasurement);
mScreenStateAtLastEnergyMeasurement = screenState;
@@ -12425,7 +12413,7 @@
return;
}
- mGlobalMeasuredEnergyStats.updateBucket(energyBucket, energyUJ, true);
+ mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ, true);
// Now we blame individual apps, but only if the display was ON.
if (energyBucket != MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON) {
@@ -12463,7 +12451,7 @@
final long appDisplayEnergyMJ =
(totalDisplayEnergyMJ * fgTimeMs + (totalFgTimeMs / 2))
/ totalFgTimeMs;
- uid.addEnergyToEnergyBucketLocked(appDisplayEnergyMJ * 1000, energyBucket, true);
+ uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket, true);
// To mitigate round-off errors, remove this app from numerator & denominator totals
totalDisplayEnergyMJ -= appDisplayEnergyMJ;
@@ -14148,33 +14136,34 @@
/**
* Initialize the measured energy stats data structures.
*
- * @param supportedEnergyBuckets boolean array indicating which buckets are currently supported
+ * @param supportedStandardBuckets boolean array indicating which {@link StandardEnergyBucket}s
+ * are currently supported.
+ * If null, none are supported (regardless of numCustomBuckets).
+ * @param numCustomBuckets number of custom (OTHER) EnergyConsumers on this device
*/
@GuardedBy("this")
- public void initMeasuredEnergyStatsLocked(boolean[] supportedEnergyBuckets) {
+ public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets,
+ int numCustomBuckets) {
boolean supportedBucketMismatch = false;
mScreenStateAtLastEnergyMeasurement = mScreenState;
- if (supportedEnergyBuckets == null) {
+ if (supportedStandardBuckets == null) {
if (mGlobalMeasuredEnergyStats != null) {
// Measured energy buckets no longer supported, wipe out the existing data.
supportedBucketMismatch = true;
}
} else if (mGlobalMeasuredEnergyStats == null) {
- mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedEnergyBuckets);
+ mGlobalMeasuredEnergyStats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
return;
} else {
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- if (mGlobalMeasuredEnergyStats.isEnergyBucketSupported(i)
- != supportedEnergyBuckets[i]) {
- mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedEnergyBuckets);
- supportedBucketMismatch = true;
- break;
- }
- }
+ supportedBucketMismatch = !mGlobalMeasuredEnergyStats.isSupportEqualTo(
+ supportedStandardBuckets, numCustomBuckets);
}
if (supportedBucketMismatch) {
+ mGlobalMeasuredEnergyStats = supportedStandardBuckets == null ?
+ null : new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
// Supported energy buckets changed since last boot.
// Existing data is no longer reliable.
resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime());
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index b8c066d..e76e34f 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.hardware.SensorManager;
-import android.net.ConnectivityManager;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
@@ -57,7 +56,7 @@
mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
- if (!isWifiOnlyDevice(mContext)) {
+ if (!BatteryStatsHelper.checkWifiOnly(mContext)) {
mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
}
mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
@@ -81,18 +80,10 @@
return mPowerCalculators;
}
- private static boolean isWifiOnlyDevice(Context context) {
- ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
- if (cm == null) {
- return false;
- }
- return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
- }
-
/**
* Returns a snapshot of battery attribution data.
*/
- public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
+ public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
// TODO(b/174186345): instead of BatteryStatsHelper, use PowerCalculators directly.
final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext,
@@ -109,28 +100,27 @@
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));
+ }
+ return results;
+ }
+
+ private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query,
+ BatteryStatsHelper batteryStatsHelper, SparseArray<UserHandle> users) {
// TODO(b/174186358): read extra power component number from configuration
final int customPowerComponentCount = 0;
final int customTimeComponentCount = 0;
- final boolean includeModeledComponents =
- (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED)
- != 0;
-
final BatteryUsageStats.Builder batteryUsageStatsBuilder =
- new BatteryUsageStats.Builder(customPowerComponentCount, customTimeComponentCount,
- includeModeledComponents)
+ new BatteryUsageStats.Builder(customPowerComponentCount, customTimeComponentCount)
.setDischargePercentage(batteryStatsHelper.getStats().getDischargeAmount(0))
.setConsumedPower(batteryStatsHelper.getTotalPower());
- final List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
- for (int i = 0; i < usageList.size(); i++) {
- final BatterySipper sipper = usageList.get(i);
- if (sipper.drainType == BatterySipper.DrainType.APP) {
- batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(sipper.uidObj)
- .setPackageWithHighestDrain(sipper.packageWithHighestDrain)
- .setConsumedPower(sipper.sumPower());
- }
+ SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats();
+ for (int i = uidStats.size() - 1; i >= 0; i--) {
+ batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
}
final long realtimeUs = SystemClock.elapsedRealtime() * 1000;
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 7972924..11c8761 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -122,11 +122,5 @@
.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, cpuTimeMs)
.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, cpuFgTimeMs)
.setPackageWithHighestDrain(packageWithHighestDrain);
-
- if ((query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED) != 0) {
- app.setConsumedPowerForCustomComponent(BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- + BatteryConsumer.POWER_COMPONENT_CPU, cpuPowerMah);
- }
}
}
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 974894f..05fcc70 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -102,7 +102,7 @@
// TODO(b/175156498): Temporary code during the transition from BatterySippers to
// BatteryConsumers.
- UidBatteryConsumer.Builder builder = new UidBatteryConsumer.Builder(0, 0, false, u);
+ 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(
@@ -162,4 +162,10 @@
// Use English locale because this is never used in UI (only in checkin and dump).
return String.format(Locale.ENGLISH, format, power);
}
+
+ static double mAhToUJ(long energyUJ) {
+ // 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 9c4a267..c86c795 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -24,7 +24,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateUtils;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseLongArray;
@@ -62,7 +62,8 @@
.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
}
- // TODO(b/178140704): Attribute screen usage similar to smearScreenBatterySipper.
+ // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
+ // TODO(b/178140704): Attribute (measured/smeared) usage *per app* for BatteryUsageStats.
}
/**
@@ -71,19 +72,45 @@
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
- final long durationMs = computeDuration(batteryStats, rawRealtimeUs, statsType);
- final double powerMah = computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
- if (powerMah != 0) {
- final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
- bs.usagePowerMah = powerMah;
- bs.usageTimeMs = durationMs;
- bs.sumPower();
- sippers.add(bs);
+ 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) {
+ 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.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);
+ } else {
smearScreenBatterySipper(sippers, bs);
}
}
+ @Override
+ protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ final long energyUJ = u.getScreenOnEnergy();
+ if (energyUJ < 0) {
+ Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
+ return;
+ }
+ if (energyUJ == 0) return;
+ app.screenPowerMah = mAhToUJ(u.getScreenOnEnergy());
+ }
+
private long computeDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
}
@@ -97,7 +124,7 @@
final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime)
* (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
if (DEBUG && binPowerMah != 0) {
- Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+ Slog.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+ " power=" + formatCharge(binPowerMah));
}
power += binPowerMah;
@@ -105,6 +132,16 @@
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.
@@ -124,10 +161,11 @@
}
if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
- final double screenPowerMah = screenSipper.totalPowerMah;
+ final double totalScreenPowerMah = screenSipper.totalPowerMah;
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper sipper = sippers.get(i);
- sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
+ sipper.screenPowerMah = totalScreenPowerMah
+ * activityTimeArray.get(sipper.getUid(), 0)
/ totalActivityTimeMs;
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fda87be..d196d4a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -103,7 +103,7 @@
*/
public static final int PROFILE_FROM_SHELL = 1 << 15;
- /*
+ /**
* Enable using the ART app image startup cache
*/
public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16;
@@ -116,14 +116,9 @@
*/
public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17;
- /**
- * Disable runtime access to {@link android.annotation.TestApi} annotated members.
- *
- * <p>This only takes effect if Hidden API access restrictions are enabled as well.
- */
- public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;
-
public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
+
+ public static final int MEMORY_TAG_LEVEL_NONE = 0;
/**
* Enable pointer tagging in this process.
* Tags are checked during memory deallocation, but not on access.
@@ -167,7 +162,12 @@
* GWP-ASan is activated unconditionally (but still, only a small subset of
* allocations is protected).
*/
- public static final int GWP_ASAN_LEVEL_ALWAYS = 2 << 21;
+ public static final int GWP_ASAN_LEVEL_ALWAYS = 1 << 22;
+
+ /**
+ * Enable automatic zero-initialization of native heap memory allocations.
+ */
+ public static final int NATIVE_HEAP_ZERO_INIT = 1 << 23;
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 3be841c..5df175e 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2540,15 +2540,16 @@
}
}
- if (a.getBoolean(R.styleable.Window_windowBackgroundBlurEnabled, false)) {
+ if (a.getBoolean(R.styleable.Window_windowBlurBehindEnabled, false)) {
if ((getForcedWindowFlags() & WindowManager.LayoutParams.FLAG_BLUR_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
}
- params.backgroundBlurRadius = a.getDimensionPixelSize(
- android.R.styleable.Window_windowBackgroundBlurRadius, 0);
+ params.blurBehindRadius = a.getDimensionPixelSize(
+ android.R.styleable.Window_windowBlurBehindRadius, 0);
}
+
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
R.styleable.Window_windowAnimationStyle, 0);
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index b744a5d..e310f8d 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -20,8 +20,10 @@
import static android.os.BatteryStats.ENERGY_DATA_UNAVAILABLE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
+import android.util.DebugUtils;
import android.util.Slog;
import android.view.Display;
@@ -33,7 +35,9 @@
import java.lang.annotation.RetentionPolicy;
/**
- * Tracks the measured energy usage of various subsystems according to their {@link EnergyBucket}.
+ * Tracks the measured energy usage of various subsystems according to their
+ * {@link StandardEnergyBucket} or custom energy bucket (which is tied to
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal}).
*
* This class doesn't use a TimeBase, and instead requires manually decisions about when to
* accumulate since it is trivial. However, in the future, a TimeBase could be used instead.
@@ -42,15 +46,13 @@
public class MeasuredEnergyStats {
private static final String TAG = "MeasuredEnergyStats";
- // Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} must be updated if energy
- // bucket integers are modified.
+ // Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} MUST be updated if standard
+ // energy bucket integers are modified/added/removed.
public static final int ENERGY_BUCKET_UNKNOWN = -1;
public static final int ENERGY_BUCKET_SCREEN_ON = 0;
public static final int ENERGY_BUCKET_SCREEN_DOZE = 1;
public static final int ENERGY_BUCKET_SCREEN_OTHER = 2;
- public static final int NUMBER_ENERGY_BUCKETS = 3;
- private static final String[] ENERGY_BUCKET_NAMES =
- {"screen-on", "screen-doze", "screen-other"};
+ public static final int NUMBER_STANDARD_ENERGY_BUCKETS = 3; // Buckets above this are custom.
@IntDef(prefix = {"ENERGY_BUCKET_"}, value = {
ENERGY_BUCKET_UNKNOWN,
@@ -59,28 +61,37 @@
ENERGY_BUCKET_SCREEN_OTHER,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface EnergyBucket {
+ public @interface StandardEnergyBucket {
}
/**
- * Total energy (in microjoules) that an {@link EnergyBucket} has accumulated since the last
- * reset. Values MUST be non-zero or ENERGY_DATA_UNAVAILABLE. Accumulation only occurs
+ * Total energy (in microjoules) that an energy bucket (including both
+ * {@link StandardEnergyBucket} and custom buckets) has accumulated since the last reset.
+ * Values MUST be non-zero or ENERGY_DATA_UNAVAILABLE. Accumulation only occurs
* while the necessary conditions are satisfied (e.g. on battery).
*
+ * Energy for both {@link StandardEnergyBucket}s and custom energy buckets are stored in this
+ * array, and may internally both referred to as 'buckets'. This is an implementation detail;
+ * externally, we differentiate between these two data sources.
+ *
* Warning: Long array is used for access speed. If the number of supported subsystems
* becomes large, consider using an alternate data structure such as a SparseLongArray.
*/
- private final long[] mAccumulatedEnergiesMicroJoules = new long[NUMBER_ENERGY_BUCKETS];
+ private final long[] mAccumulatedEnergiesMicroJoules;
/**
* Creates a MeasuredEnergyStats set to support the provided energy buckets.
- * supportedEnergyBuckets should generally be of size {@link #NUMBER_ENERGY_BUCKETS}.
+ * supportedStandardBuckets must be of size {@link #NUMBER_STANDARD_ENERGY_BUCKETS}.
+ * numCustomBuckets >= 0 is the number of (non-standard) custom energy buckets on the device.
*/
- public MeasuredEnergyStats(boolean[] supportedEnergyBuckets) {
+ public MeasuredEnergyStats(boolean[] supportedStandardBuckets, int numCustomBuckets) {
+ final int numTotalBuckets = NUMBER_STANDARD_ENERGY_BUCKETS + numCustomBuckets;
+ mAccumulatedEnergiesMicroJoules = new long[numTotalBuckets];
// Initialize to all zeros where supported, otherwise ENERGY_DATA_UNAVAILABLE.
- for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
- if (!supportedEnergyBuckets[bucket]) {
- mAccumulatedEnergiesMicroJoules[bucket] = ENERGY_DATA_UNAVAILABLE;
+ // All custom buckets are, by definition, supported, so their values stay at 0.
+ for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_ENERGY_BUCKETS; stdBucket++) {
+ if (!supportedStandardBuckets[stdBucket]) {
+ mAccumulatedEnergiesMicroJoules[stdBucket] = ENERGY_DATA_UNAVAILABLE;
}
}
}
@@ -90,10 +101,13 @@
* supported. This certainly does NOT produce an exact clone of the template.
*/
private MeasuredEnergyStats(MeasuredEnergyStats template) {
+ final int numIndices = template.getNumberOfIndices();
+ mAccumulatedEnergiesMicroJoules = new long[numIndices];
// Initialize to all zeros where supported, otherwise ENERGY_DATA_UNAVAILABLE.
- for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
- if (!template.isEnergyBucketSupported(bucket)) {
- mAccumulatedEnergiesMicroJoules[bucket] = ENERGY_DATA_UNAVAILABLE;
+ // All custom buckets are, by definition, supported, so their values stay at 0.
+ for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_ENERGY_BUCKETS; stdBucket++) {
+ if (!template.isIndexSupported(stdBucket)) {
+ mAccumulatedEnergiesMicroJoules[stdBucket] = ENERGY_DATA_UNAVAILABLE;
}
}
}
@@ -108,18 +122,22 @@
/**
* Constructor for creating a temp MeasuredEnergyStats.
- * See {@link #readSummaryFromParcel(MeasuredEnergyStats, Parcel)}.
+ * See {@link #createAndReadSummaryFromParcel(Parcel, MeasuredEnergyStats)}.
*/
- private MeasuredEnergyStats() {
+ private MeasuredEnergyStats(int numIndices) {
+ mAccumulatedEnergiesMicroJoules = new long[numIndices];
}
/** Construct from parcel. */
public MeasuredEnergyStats(Parcel in) {
+ final int size = in.readInt();
+ mAccumulatedEnergiesMicroJoules = new long[size];
in.readLongArray(mAccumulatedEnergiesMicroJoules);
}
/** Write to parcel */
public void writeToParcel(Parcel out) {
+ out.writeInt(mAccumulatedEnergiesMicroJoules.length);
out.writeLongArray(mAccumulatedEnergiesMicroJoules);
}
@@ -129,16 +147,18 @@
* summary parcel was written. Availability has already been correctly set in the constructor.
* Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} must be updated if summary
* parceling changes.
+ *
+ * Corresponding write performed by {@link #writeSummaryToParcel(Parcel, boolean)}.
*/
private void readSummaryFromParcel(Parcel in, boolean overwriteAvailability) {
- final int size = in.readInt();
- for (int i = 0; i < size; i++) {
- final int bucket = in.readInt();
+ final int numWrittenEntries = in.readInt();
+ for (int entry = 0; entry < numWrittenEntries; entry++) {
+ final int index = in.readInt();
final long energyUJ = in.readLong();
if (overwriteAvailability) {
- mAccumulatedEnergiesMicroJoules[bucket] = energyUJ;
+ mAccumulatedEnergiesMicroJoules[index] = energyUJ;
} else {
- setValueIfSupported(bucket, energyUJ);
+ setValueIfSupported(index, energyUJ);
}
}
}
@@ -146,52 +166,90 @@
/**
* Write to summary parcel.
* Note: Measured subsystem availability may be different when the summary parcel is read.
+ *
+ * Corresponding read performed by {@link #readSummaryFromParcel(Parcel, boolean)}.
*/
private void writeSummaryToParcel(Parcel out, boolean skipZero) {
- final int sizePos = out.dataPosition();
+ final int posOfNumWrittenEntries = out.dataPosition();
out.writeInt(0);
- int size = 0;
- // Write only the supported buckets with non-zero energy.
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- final long energy = mAccumulatedEnergiesMicroJoules[i];
+ int numWrittenEntries = 0;
+ // Write only the supported buckets (with non-zero energy, if applicable).
+ for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
+ final long energy = mAccumulatedEnergiesMicroJoules[index];
if (energy < 0) continue;
if (energy == 0 && skipZero) continue;
- out.writeInt(i);
- out.writeLong(mAccumulatedEnergiesMicroJoules[i]);
- size++;
+ out.writeInt(index);
+ out.writeLong(mAccumulatedEnergiesMicroJoules[index]);
+ numWrittenEntries++;
}
final int currPos = out.dataPosition();
- out.setDataPosition(sizePos);
- out.writeInt(size);
+ out.setDataPosition(posOfNumWrittenEntries);
+ out.writeInt(numWrittenEntries);
out.setDataPosition(currPos);
}
- /** Updates the given bucket with the given energy iff accumulate is true. */
- public void updateBucket(@EnergyBucket int bucket, long energyDeltaUJ, boolean accumulate) {
+ /** Get number of possible buckets, including both standard and custom ones. */
+ private int getNumberOfIndices() {
+ return mAccumulatedEnergiesMicroJoules.length;
+ }
+
+ // TODO: Get rid of the 'accumulate' boolean. It's always true.
+ /** Updates the given standard energy bucket with the given energy if accumulate is true. */
+ public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ,
+ boolean accumulate) {
+ checkValidStandardBucket(bucket);
+ updateEntry(bucket, energyDeltaUJ, accumulate);
+ }
+
+ /** Updates the given custom energy bucket with the given energy if accumulate is true. */
+ public void updateCustomBucket(int customBucket, long energyDeltaUJ, boolean accumulate) {
+ if (!isValidCustomBucket(customBucket)) {
+ Slog.e(TAG, "Attempted to update invalid custom bucket " + customBucket);
+ return;
+ }
+ final int index = customBucketToIndex(customBucket);
+ updateEntry(index, energyDeltaUJ, accumulate);
+ }
+
+ /** Updates the given index with the given energy if accumulate is true. */
+ private void updateEntry(int index, long energyDeltaUJ, boolean accumulate) {
if (accumulate) {
- if (mAccumulatedEnergiesMicroJoules[bucket] >= 0L) {
- mAccumulatedEnergiesMicroJoules[bucket] += energyDeltaUJ;
+ if (mAccumulatedEnergiesMicroJoules[index] >= 0L) {
+ mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ;
} else {
Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket "
- + ENERGY_BUCKET_NAMES[bucket] + " whose value was "
- + mAccumulatedEnergiesMicroJoules[bucket]);
+ + getBucketName(index) + " whose value was "
+ + mAccumulatedEnergiesMicroJoules[index]);
}
}
}
/**
- * Return accumulated energy (in microjoules) for the given energy bucket since last reset.
- * Returns {@link BatteryStats#ENERGY_DATA_UNAVAILABLE} if this energy data is unavailable.
+ * Return accumulated energy (in microjoules) for a standard energy bucket since last reset.
+ * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
+ * @throws IllegalArgumentException if no such {@link StandardEnergyBucket}.
*/
- public long getAccumulatedBucketEnergy(@EnergyBucket int bucket) {
+ public long getAccumulatedStandardBucketEnergy(@StandardEnergyBucket int bucket) {
+ checkValidStandardBucket(bucket);
return mAccumulatedEnergiesMicroJoules[bucket];
}
/**
- * Map {@link MeasuredEnergySubsystem} and device state to a Display {@link EnergyBucket}.
+ * Return accumulated energy (in microjoules) for the a custom energy bucket since last reset.
+ * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
*/
- public static @EnergyBucket int getDisplayEnergyBucket(int screenState) {
+ public long getAccumulatedCustomBucketEnergy(int customBucket) {
+ if (!isValidCustomBucket(customBucket)) {
+ return ENERGY_DATA_UNAVAILABLE;
+ }
+ return mAccumulatedEnergiesMicroJoules[customBucketToIndex(customBucket)];
+ }
+
+ /**
+ * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+ */
+ public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
if (Display.isOnState(screenState)) {
return ENERGY_BUCKET_SCREEN_ON;
}
@@ -204,15 +262,20 @@
/**
* Create a MeasuredEnergyStats object from a summary parcel.
*
+ * Corresponding write performed by
+ * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel, boolean)}.
+ *
* @return a new MeasuredEnergyStats object as described.
* Returns null if the parcel indicates there is no data to populate.
*/
public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(Parcel in) {
+ final int arraySize = in.readInt();
// Check if any MeasuredEnergyStats exists on the parcel
- if (in.readInt() == 0) return null;
+ if (arraySize == 0) return null;
- final MeasuredEnergyStats stats =
- new MeasuredEnergyStats(new boolean[NUMBER_ENERGY_BUCKETS]);
+ final int numCustomBuckets = arraySize - NUMBER_STANDARD_ENERGY_BUCKETS;
+ final MeasuredEnergyStats stats = new MeasuredEnergyStats(
+ new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], numCustomBuckets);
stats.readSummaryFromParcel(in, true);
return stats;
}
@@ -221,6 +284,12 @@
* Create a MeasuredEnergyStats using the template to determine which buckets are supported,
* and populate this new object from the given parcel.
*
+ * The parcel must be consistent with the template in terms of the number of
+ * possible (not necessarily supported) standard and custom buckets.
+ *
+ * Corresponding write performed by
+ * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel, boolean)}.
+ *
* @return a new MeasuredEnergyStats object as described.
* Returns null if the stats contain no non-0 information (such as if template is null
* or if the parcel indicates there is no data to populate).
@@ -229,12 +298,22 @@
*/
public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(Parcel in,
@Nullable MeasuredEnergyStats template) {
+ final int arraySize = in.readInt();
// Check if any MeasuredEnergyStats exists on the parcel
- if (in.readInt() == 0) return null;
+ if (arraySize == 0) return null;
if (template == null) {
- // Nothing supported now. Create placeholder object just to consume the parcel data.
- final MeasuredEnergyStats mes = new MeasuredEnergyStats();
+ // Nothing supported anymore. Create placeholder object just to consume the parcel data.
+ final MeasuredEnergyStats mes = new MeasuredEnergyStats(arraySize);
+ mes.readSummaryFromParcel(in, false);
+ return null;
+ }
+
+ if (arraySize != template.getNumberOfIndices()) {
+ Slog.wtf(TAG, "Size of MeasuredEnergyStats parcel (" + arraySize
+ + ") does not match template (" + template.getNumberOfIndices() + ").");
+ // Something is horribly wrong. Just consume the parcel and return null.
+ final MeasuredEnergyStats mes = new MeasuredEnergyStats(arraySize);
mes.readSummaryFromParcel(in, false);
return null;
}
@@ -251,14 +330,17 @@
/** Returns true iff any of the buckets are supported and non-zero. */
private boolean containsInterestingData() {
- for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
- if (mAccumulatedEnergiesMicroJoules[bucket] > 0) return true;
+ for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
+ if (mAccumulatedEnergiesMicroJoules[index] > 0) return true;
}
return false;
}
/**
* Write a MeasuredEnergyStats to a parcel. If the stats is null, just write a 0.
+ *
+ * Corresponding read performed by {@link #createAndReadSummaryFromParcel(Parcel)}
+ * and {@link #createAndReadSummaryFromParcel(Parcel, MeasuredEnergyStats)}.
*/
public static void writeSummaryToParcel(@Nullable MeasuredEnergyStats stats,
Parcel dest, boolean skipZero) {
@@ -266,14 +348,15 @@
dest.writeInt(0);
return;
}
- dest.writeInt(1);
+ dest.writeInt(stats.getNumberOfIndices());
stats.writeSummaryToParcel(dest, skipZero);
}
/** Reset accumulated energy. */
private void reset() {
- for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
- setValueIfSupported(bucket, 0L);
+ final int numIndices = getNumberOfIndices();
+ for (int index = 0; index < numIndices; index++) {
+ setValueIfSupported(index, 0L);
}
}
@@ -282,33 +365,93 @@
if (stats != null) stats.reset();
}
- /** If the bucket is AVAILABLE, overwrite its value; otherwise leave it as UNAVAILABLE. */
- private void setValueIfSupported(@EnergyBucket int bucket, long value) {
- if (mAccumulatedEnergiesMicroJoules[bucket] != ENERGY_DATA_UNAVAILABLE) {
- mAccumulatedEnergiesMicroJoules[bucket] = value;
+ /** If the index is AVAILABLE, overwrite its value; otherwise leave it as UNAVAILABLE. */
+ private void setValueIfSupported(int index, long value) {
+ if (mAccumulatedEnergiesMicroJoules[index] != ENERGY_DATA_UNAVAILABLE) {
+ mAccumulatedEnergiesMicroJoules[index] = value;
}
}
- /** Check if measuring the energy of the given bucket is supported by this device. */
- public boolean isEnergyBucketSupported(@EnergyBucket int bucket) {
- return mAccumulatedEnergiesMicroJoules[bucket] != ENERGY_DATA_UNAVAILABLE;
+ /**
+ * Check if measuring the energy of the given bucket is supported by this device.
+ * @throws IllegalArgumentException if not a valid {@link StandardEnergyBucket}.
+ */
+ public boolean isStandardBucketSupported(@StandardEnergyBucket int bucket) {
+ checkValidStandardBucket(bucket);
+ return isIndexSupported(bucket);
+ }
+
+ private boolean isIndexSupported(int index) {
+ return mAccumulatedEnergiesMicroJoules[index] != ENERGY_DATA_UNAVAILABLE;
+ }
+
+ /** Check if the supported energy buckets are precisely those given. */
+ public boolean isSupportEqualTo(
+ @NonNull boolean[] queriedStandardBuckets, int numCustomBuckets) {
+
+ final int numBuckets = getNumberOfIndices();
+ // TODO(b/178504428): Detect whether custom buckets have changed qualitatively, not just
+ // quantitatively, and treat as mismatch if so.
+ if (numBuckets != NUMBER_STANDARD_ENERGY_BUCKETS + numCustomBuckets) {
+ return false;
+ }
+ for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_ENERGY_BUCKETS; stdBucket++) {
+ if (isStandardBucketSupported(stdBucket) != queriedStandardBuckets[stdBucket]) {
+ return false;
+ }
+ }
+ return true;
}
/** Dump debug data. */
public void dump(PrintWriter pw) {
pw.println("Accumulated energy since last reset (microjoules):");
pw.print(" ");
- for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
- pw.print(ENERGY_BUCKET_NAMES[bucket]);
+ for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
+ pw.print(getBucketName(index));
pw.print(" : ");
- pw.print(mAccumulatedEnergiesMicroJoules[bucket]);
- if (!isEnergyBucketSupported(bucket)) {
+ pw.print(mAccumulatedEnergiesMicroJoules[index]);
+ if (!isIndexSupported(index)) {
pw.print(" (unsupported)");
}
- if (bucket != NUMBER_ENERGY_BUCKETS - 1) {
+ if (index != mAccumulatedEnergiesMicroJoules.length - 1) {
pw.print(", ");
}
}
pw.println();
}
+
+ /**
+ * If the index is a standard bucket, returns its name; otherwise returns its prefixed custom
+ * bucket number.
+ */
+ private static String getBucketName(int index) {
+ if (isValidStandardBucket(index)) {
+ return DebugUtils.valueToString(MeasuredEnergyStats.class, "ENERGY_BUCKET_", index);
+ }
+ return "CUSTOM_" + indexToCustomBucket(index);
+ }
+
+ private static int customBucketToIndex(int customBucket) {
+ return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS;
+ }
+
+ private static int indexToCustomBucket(int index) {
+ return index - NUMBER_STANDARD_ENERGY_BUCKETS;
+ }
+
+ private static void checkValidStandardBucket(@StandardEnergyBucket int bucket) {
+ if (!isValidStandardBucket(bucket)) {
+ throw new IllegalArgumentException("Illegal StandardEnergyBucket " + bucket);
+ }
+ }
+
+ private static boolean isValidStandardBucket(@StandardEnergyBucket int bucket) {
+ return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
+ }
+
+ private boolean isValidCustomBucket(int customBucket) {
+ return customBucket >= 0
+ && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
+ }
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 3cf00ae..c6fd6ee 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -35,6 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.IntFunction;
/**
@@ -599,6 +600,20 @@
return cur;
}
+ /**
+ * Similar to {@link Set#addAll(Collection)}}, but with support for set values of {@code null}.
+ */
+ public static @NonNull <T> ArraySet<T> addAll(@Nullable ArraySet<T> cur,
+ @Nullable Collection<T> val) {
+ if (cur == null) {
+ cur = new ArraySet<>();
+ }
+ if (val != null) {
+ cur.addAll(val);
+ }
+ return cur;
+ }
+
public static @Nullable <T> ArraySet<T> remove(@Nullable ArraySet<T> cur, T val) {
if (cur == null) {
return null;
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 30cd94c..f42f468 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -14,6 +14,7 @@
package com.android.internal.util;
+import android.annotation.IntDef;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
@@ -23,9 +24,12 @@
import android.util.Log;
import android.util.SparseLongArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.EventLogTags;
import com.android.internal.os.BackgroundThread;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.ThreadLocalRandom;
/**
@@ -91,6 +95,34 @@
*/
public static final int ACTION_START_RECENTS_ANIMATION = 8;
+ private static final int[] ACTIONS_ALL = {
+ ACTION_EXPAND_PANEL,
+ ACTION_TOGGLE_RECENTS,
+ ACTION_FINGERPRINT_WAKE_AND_UNLOCK,
+ ACTION_CHECK_CREDENTIAL,
+ ACTION_CHECK_CREDENTIAL_UNLOCKED,
+ ACTION_TURN_ON_SCREEN,
+ ACTION_ROTATE_SCREEN,
+ ACTION_FACE_WAKE_AND_UNLOCK,
+ ACTION_START_RECENTS_ANIMATION
+ };
+
+ /** @hide */
+ @IntDef({
+ ACTION_EXPAND_PANEL,
+ ACTION_TOGGLE_RECENTS,
+ ACTION_FINGERPRINT_WAKE_AND_UNLOCK,
+ ACTION_CHECK_CREDENTIAL,
+ ACTION_CHECK_CREDENTIAL_UNLOCKED,
+ ACTION_TURN_ON_SCREEN,
+ ACTION_ROTATE_SCREEN,
+ ACTION_FACE_WAKE_AND_UNLOCK,
+ ACTION_START_RECENTS_ANIMATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {
+ }
+
private static final int[] STATSD_ACTION = new int[]{
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS,
@@ -105,9 +137,14 @@
private static LatencyTracker sLatencyTracker;
+ private final Object mLock = new Object();
private final SparseLongArray mStartRtc = new SparseLongArray();
- private volatile int mSamplingInterval;
- private volatile boolean mEnabled;
+ @GuardedBy("mLock")
+ private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length];
+ @GuardedBy("mLock")
+ private int mSamplingInterval;
+ @GuardedBy("mLock")
+ private boolean mEnabled;
public static LatencyTracker getInstance(Context context) {
if (sLatencyTracker == null) {
@@ -132,20 +169,26 @@
}
private void updateProperties(DeviceConfig.Properties properties) {
- mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
- DEFAULT_SAMPLING_INTERVAL);
- mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+ synchronized (mLock) {
+ mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
+ DEFAULT_SAMPLING_INTERVAL);
+ mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+ for (int action : ACTIONS_ALL) {
+ mTraceThresholdPerAction[action] =
+ properties.getInt(getTraceTriggerNameForAction(action), -1);
+ }
+ }
}
/**
* A helper method to translate action type to name.
*
- * @param action the action type defined in AtomsProto.java
+ * @param atomsProtoAction the action type defined in AtomsProto.java
* @return the name of the action
*/
- public static String getNameOfAction(int action) {
+ public static String getNameOfAction(int atomsProtoAction) {
// Defined in AtomsProto.java
- switch (action) {
+ switch (atomsProtoAction) {
case 0:
return "UNKNOWN";
case 1:
@@ -171,16 +214,22 @@
}
}
- private static String getTraceNameOfAction(int action) {
+ private static String getTraceNameOfAction(@Action int action) {
return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">";
}
+ private static String getTraceTriggerNameForAction(@Action int action) {
+ return "latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
+ }
+
public static boolean isEnabled(Context ctx) {
return getInstance(ctx).isEnabled();
}
public boolean isEnabled() {
- return mEnabled;
+ synchronized (mLock) {
+ return mEnabled;
+ }
}
/**
@@ -188,7 +237,7 @@
*
* @param action The action to start. One of the ACTION_* values.
*/
- public void onActionStart(int action) {
+ public void onActionStart(@Action int action) {
if (!isEnabled()) {
return;
}
@@ -201,7 +250,7 @@
*
* @param action The action to end. One of the ACTION_* values.
*/
- public void onActionEnd(int action) {
+ public void onActionEnd(@Action int action) {
if (!isEnabled()) {
return;
}
@@ -221,19 +270,30 @@
* @param action The action to end. One of the ACTION_* values.
* @param duration The duration of the action in ms.
*/
- public void logAction(int action, int duration) {
- boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ public void logAction(@Action int action, int duration) {
+ boolean shouldSample;
+ int traceThreshold;
+ synchronized (mLock) {
+ shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ traceThreshold = mTraceThresholdPerAction[action];
+ }
+
+ if (traceThreshold > 0 && duration >= traceThreshold) {
+ PerfettoTrigger.trigger(getTraceTriggerNameForAction(action));
+ }
+
logActionDeprecated(action, duration, shouldSample);
}
/**
* Logs an action that has started and ended. This needs to be called from the main thread.
*
- * @param action The action to end. One of the ACTION_* values.
- * @param duration The duration of the action in ms.
+ * @param action The action to end. One of the ACTION_* values.
+ * @param duration The duration of the action in ms.
* @param writeToStatsLog Whether to write the measured latency to FrameworkStatsLog.
*/
- public static void logActionDeprecated(int action, int duration, boolean writeToStatsLog) {
+ public static void logActionDeprecated(
+ @Action int action, int duration, boolean writeToStatsLog) {
Log.i(TAG, getNameOfAction(STATSD_ACTION[action]) + " latency=" + duration);
EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration);
diff --git a/core/java/com/android/internal/util/Parcelling.java b/core/java/com/android/internal/util/Parcelling.java
index dd64c40..1ab316d 100644
--- a/core/java/com/android/internal/util/Parcelling.java
+++ b/core/java/com/android/internal/util/Parcelling.java
@@ -27,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.regex.Pattern;
/**
@@ -291,5 +292,19 @@
return s == null ? null : Pattern.compile(s);
}
}
+
+ class ForUUID implements Parcelling<UUID> {
+
+ @Override
+ public void parcel(UUID item, Parcel dest, int parcelFlags) {
+ dest.writeString(item == null ? null : item.toString());
+ }
+
+ @Override
+ public UUID unparcel(Parcel source) {
+ String string = source.readString();
+ return string == null ? null : UUID.fromString(string);
+ }
+ }
}
}
diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java
new file mode 100644
index 0000000..9c87c69
--- /dev/null
+++ b/core/java/com/android/internal/util/PerfettoTrigger.java
@@ -0,0 +1,44 @@
+/*
+ * 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.util;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * A trigger implementation with perfetto backend.
+ * @hide
+ */
+public class PerfettoTrigger {
+ private static final String TAG = "PerfettoTrigger";
+ private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
+
+ /**
+ * @param triggerName The name of the trigger. Must match the value defined in the AOT
+ * Perfetto config.
+ */
+ public static void trigger(String triggerName) {
+ try {
+ ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
+ Log.v(TAG, "Triggering " + String.join(" ", pb.command()));
+ Process process = pb.start();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to trigger " + triggerName, e);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 892c5a5..50bbfc5 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -91,11 +91,12 @@
/** Remove the IME surface. Requires passing the currently focused window. */
oneway void removeImeSurfaceFromWindow(in IBinder windowToken,
in IVoidResultCallback resultCallback);
- void startProtoDump(in byte[] protoDump, int source, String where);
+ oneway void startProtoDump(in byte[] protoDump, int source, String where,
+ in IVoidResultCallback resultCallback);
oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
// Starts an ime trace.
- void startImeTrace();
+ oneway void startImeTrace(in IVoidResultCallback resultCallback);
// Stops an ime trace.
- void stopImeTrace();
+ oneway void stopImeTrace(in IVoidResultCallback resultCallback);
}
diff --git a/core/java/com/android/internal/view/ScrollCaptureInternal.java b/core/java/com/android/internal/view/ScrollCaptureInternal.java
index ae1a815..4b9a160 100644
--- a/core/java/com/android/internal/view/ScrollCaptureInternal.java
+++ b/core/java/com/android/internal/view/ScrollCaptureInternal.java
@@ -59,6 +59,11 @@
public static final int TYPE_RECYCLING = 2;
/**
+ * The ViewGroup scrolls, but has no child views in
+ */
+ private static final int TYPE_OPAQUE = 3;
+
+ /**
* Performs tests on the given View and determines:
* 1. If scrolling is possible
* 2. What mechanisms are used for scrolling.
@@ -95,8 +100,15 @@
}
return TYPE_RECYCLING;
}
+ // At least one child view is required.
+ if (((ViewGroup) view).getChildCount() < 1) {
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "scrollable with no children");
+ }
+ return TYPE_OPAQUE;
+ }
if (DEBUG_VERBOSE) {
- Log.v(TAG, "hint: less than two child views");
+ Log.v(TAG, "hint: single child view");
}
//Because recycling containers don't use scrollY, a non-zero value means Scroll view.
if (view.getScrollY() != 0) {
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index bad21d2..73c5460 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -148,6 +148,7 @@
private int mRegularColor;
private int mErrorColor;
private int mSuccessColor;
+ private int mDotColor;
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mLinearOutSlowInInterpolator;
@@ -318,6 +319,7 @@
mRegularColor = a.getColor(R.styleable.LockPatternView_regularColor, 0);
mErrorColor = a.getColor(R.styleable.LockPatternView_errorColor, 0);
mSuccessColor = a.getColor(R.styleable.LockPatternView_successColor, 0);
+ mDotColor = a.getColor(R.styleable.LockPatternView_dotColor, mRegularColor);
int pathColor = a.getColor(R.styleable.LockPatternView_pathColor, mRegularColor);
mPathPaint.setColor(pathColor);
@@ -522,7 +524,7 @@
getCenterYForRow(cellState.row) + startTranslationY);
cellState.hwCenterX = CanvasProperty.createFloat(getCenterXForColumn(cellState.col));
cellState.hwRadius = CanvasProperty.createFloat(mDotSize/2 * startScale);
- mPaint.setColor(getCurrentColor(false));
+ mPaint.setColor(getDotColor());
mPaint.setAlpha((int) (startAlpha * 255));
cellState.hwPaint = CanvasProperty.createPaint(new Paint(mPaint));
@@ -1163,29 +1165,6 @@
final Path currentPath = mCurrentPath;
currentPath.rewind();
- // draw the circles
- for (int i = 0; i < 3; i++) {
- float centerY = getCenterYForRow(i);
- for (int j = 0; j < 3; j++) {
- CellState cellState = mCellStates[i][j];
- float centerX = getCenterXForColumn(j);
- float translationY = cellState.translationY;
-
- if (mUseLockPatternDrawable) {
- drawCellDrawable(canvas, i, j, cellState.radius, drawLookup[i][j]);
- } else {
- if (isHardwareAccelerated() && cellState.hwAnimating) {
- RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
- recordingCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,
- cellState.hwRadius, cellState.hwPaint);
- } else {
- drawCircle(canvas, (int) centerX, (int) centerY + translationY,
- cellState.radius, drawLookup[i][j], cellState.alpha);
- }
- }
- }
- }
-
// TODO: the path should be created and cached every time we hit-detect a cell
// only the last segment of the path should be computed here
// draw the path of the pattern (unless we are in stealth mode)
@@ -1256,6 +1235,29 @@
canvas.drawPath(currentPath, mPathPaint);
}
}
+
+ // draw the circles
+ for (int i = 0; i < 3; i++) {
+ float centerY = getCenterYForRow(i);
+ for (int j = 0; j < 3; j++) {
+ CellState cellState = mCellStates[i][j];
+ float centerX = getCenterXForColumn(j);
+ float translationY = cellState.translationY;
+
+ if (mUseLockPatternDrawable) {
+ drawCellDrawable(canvas, i, j, cellState.radius, drawLookup[i][j]);
+ } else {
+ if (isHardwareAccelerated() && cellState.hwAnimating) {
+ RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+ recordingCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,
+ cellState.hwRadius, cellState.hwPaint);
+ } else {
+ drawCircle(canvas, (int) centerX, (int) centerY + translationY,
+ cellState.radius, drawLookup[i][j], cellState.alpha);
+ }
+ }
+ }
+ }
}
private float calculateLastSegmentAlpha(float x, float y, float lastX, float lastY) {
@@ -1266,6 +1268,17 @@
return Math.min(1f, Math.max(0f, (frac - 0.3f) * 4f));
}
+ private int getDotColor() {
+ if (mInStealthMode) {
+ // Always use the default color in this case
+ return mDotColor;
+ } else if (mPatternDisplayMode == DisplayMode.Wrong) {
+ // the pattern is wrong
+ return mErrorColor;
+ }
+ return mDotColor;
+ }
+
private int getCurrentColor(boolean partOfPattern) {
if (!partOfPattern || mInStealthMode || mPatternInProgress) {
// unselected circle
@@ -1286,7 +1299,7 @@
*/
private void drawCircle(Canvas canvas, float centerX, float centerY, float radius,
boolean partOfPattern, float alpha) {
- mPaint.setColor(getCurrentColor(partOfPattern));
+ mPaint.setColor(getDotColor());
mPaint.setAlpha((int) (alpha * 255));
canvas.drawCircle(centerX, centerY, radius, mPaint);
}
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index ae566c3..3fc3933 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -5,3 +5,16 @@
per-file *LockScreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file *LockSettings* = file:/services/core/java/com/android/server/locksettings/OWNERS
+
+# Notification related
+per-file *Notification* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *Messaging* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *Message* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *Conversation* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *ImageResolver* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file CallLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file CachingIconView.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file ImageFloatingTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file ObservableTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file RemeasuringLinearLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file ViewClippingUtil.java = file:/services/core/java/com/android/server/notification/OWNERS
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index e5bc470..cb586d6 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -178,8 +178,9 @@
// be delivered anonymously even to apps which target O+.
final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
- // These are the package names of apps which should be in the 'always'
- // URL-handling state upon factory reset.
+ // These are the package names of apps which should be automatically granted domain verification
+ // for all of their domains. The only way these apps can be overridden by the user is by
+ // explicitly disabling overall link handling support in app info.
final ArraySet<String> mLinkedApps = new ArraySet<>();
// These are the components that are enabled by default as VR mode listener services.
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 66753e4..f1998a5 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -47,6 +47,7 @@
static struct assetsprovider_offsets_t {
jclass classObject;
jmethodID loadAssetFd;
+ jmethodID toString;
} gAssetsProviderOffsets;
static struct {
@@ -72,9 +73,22 @@
class LoaderAssetsProvider : public AssetsProvider {
public:
static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
- return (!assets_provider) ? nullptr
+ return (!assets_provider) ? EmptyAssetsProvider::Create()
: std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
- env->NewGlobalRef(assets_provider)));
+ env, assets_provider));
+ }
+
+ bool ForEachFile(const std::string& /* root_path */,
+ const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+ return true;
+ }
+
+ const std::string& GetDebugName() const override {
+ return debug_name_;
+ }
+
+ bool IsUpToDate() const override {
+ return true;
}
~LoaderAssetsProvider() override {
@@ -142,20 +156,26 @@
*file_exists = true;
}
- return ApkAssets::CreateAssetFromFd(base::unique_fd(fd),
- nullptr /* path */,
- static_cast<off64_t>(mOffset),
- static_cast<off64_t>(mLength));
+ return AssetsProvider::CreateAssetFromFd(base::unique_fd(fd),
+ nullptr /* path */,
+ static_cast<off64_t>(mOffset),
+ static_cast<off64_t>(mLength));
}
private:
DISALLOW_COPY_AND_ASSIGN(LoaderAssetsProvider);
- explicit LoaderAssetsProvider(jobject assets_provider)
- : assets_provider_(assets_provider) { }
+ explicit LoaderAssetsProvider(JNIEnv* env, jobject assets_provider) {
+ assets_provider_ = env->NewGlobalRef(assets_provider);
+ auto string_result = static_cast<jstring>(env->CallObjectMethod(
+ assets_provider_, gAssetsProviderOffsets.toString));
+ ScopedUtfChars str(env, string_result);
+ debug_name_ = std::string(str.c_str(), str.size());
+ }
// The global reference to the AssetsProvider
jobject assets_provider_;
+ std::string debug_name_;
};
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
@@ -170,18 +190,26 @@
auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr<const ApkAssets> apk_assets;
switch (format) {
- case FORMAT_APK:
- apk_assets = ApkAssets::Load(path.c_str(), property_flags, std::move(loader_assets));
+ case FORMAT_APK: {
+ auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
+ ZipAssetsProvider::Create(path.c_str()));
+ apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
+ }
case FORMAT_IDMAP:
apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
break;
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags, std::move(loader_assets));
+ apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
+ std::move(loader_assets),
+ property_flags);
break;
- case FORMAT_DIRECTORY:
- apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags, std::move(loader_assets));
+ case FORMAT_DIRECTORY: {
+ auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
+ DirectoryAssetsProvider::Create(path.c_str()));
+ apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
+ }
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -221,13 +249,17 @@
auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr<const ApkAssets> apk_assets;
switch (format) {
- case FORMAT_APK:
- apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, std::move(loader_assets));
+ case FORMAT_APK: {
+ auto assets = MultiAssetsProvider::Create(
+ std::move(loader_assets),
+ ZipAssetsProvider::Create(std::move(dup_fd), friendly_name_utf8.c_str()));
+ apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
+ }
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, std::move(loader_assets));
+ apk_assets = ApkAssets::LoadTable(
+ AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
+ std::move(loader_assets), property_flags);
break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
@@ -282,17 +314,20 @@
auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr<const ApkAssets> apk_assets;
switch (format) {
- case FORMAT_APK:
- apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, std::move(loader_assets),
- static_cast<off64_t>(offset),
- static_cast<off64_t>(length));
+ case FORMAT_APK: {
+ auto assets = MultiAssetsProvider::Create(
+ std::move(loader_assets),
+ ZipAssetsProvider::Create(std::move(dup_fd), friendly_name_utf8.c_str(),
+ static_cast<off64_t>(offset), static_cast<off64_t>(length)));
+ apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
+ }
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, std::move(loader_assets),
- static_cast<off64_t>(offset),
- static_cast<off64_t>(length));
+ apk_assets = ApkAssets::LoadTable(
+ AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
+ static_cast<off64_t>(offset),
+ static_cast<off64_t>(length)),
+ std::move(loader_assets), property_flags);
break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
@@ -310,8 +345,7 @@
}
static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
- auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
- auto apk_assets = ApkAssets::LoadEmpty(flags, std::move(loader_assets));
+ auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
return reinterpret_cast<jlong>(apk_assets.release());
}
@@ -458,6 +492,8 @@
gAssetsProviderOffsets.loadAssetFd = GetMethodIDOrDie(
env, gAssetsProviderOffsets.classObject, "loadAssetFd",
"(Ljava/lang/String;I)Landroid/content/res/AssetFileDescriptor;");
+ gAssetsProviderOffsets.toString = GetMethodIDOrDie(
+ env, gAssetsProviderOffsets.classObject, "toString", "()Ljava/lang/String;");
jclass parcelFd = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
gParcelFileDescriptorOffsets.detachFd = GetMethodIDOrDie(env, parcelFd, "detachFd", "()I");
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 5f2dcdf..5630a1e 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -41,6 +41,10 @@
#define ENCODING_OPUS 20
#define ENCODING_PCM_24BIT_PACKED 21
#define ENCODING_PCM_32BIT 22
+#define ENCODING_MPEGH_BL_L3 23
+#define ENCODING_MPEGH_BL_L4 24
+#define ENCODING_MPEGH_LC_L3 25
+#define ENCODING_MPEGH_LC_L4 26
#define ENCODING_INVALID 0
#define ENCODING_DEFAULT 1
@@ -98,6 +102,14 @@
return AUDIO_FORMAT_PCM_24_BIT_PACKED;
case ENCODING_PCM_32BIT:
return AUDIO_FORMAT_PCM_32_BIT;
+ case ENCODING_MPEGH_BL_L3:
+ return AUDIO_FORMAT_MPEGH_BL_L3;
+ case ENCODING_MPEGH_BL_L4:
+ return AUDIO_FORMAT_MPEGH_BL_L4;
+ case ENCODING_MPEGH_LC_L3:
+ return AUDIO_FORMAT_MPEGH_LC_L3;
+ case ENCODING_MPEGH_LC_L4:
+ return AUDIO_FORMAT_MPEGH_LC_L4;
default:
return AUDIO_FORMAT_INVALID;
}
@@ -159,6 +171,14 @@
return ENCODING_DOLBY_MAT;
case AUDIO_FORMAT_OPUS:
return ENCODING_OPUS;
+ case AUDIO_FORMAT_MPEGH_BL_L3:
+ return ENCODING_MPEGH_BL_L3;
+ case AUDIO_FORMAT_MPEGH_BL_L4:
+ return ENCODING_MPEGH_BL_L4;
+ case AUDIO_FORMAT_MPEGH_LC_L3:
+ return ENCODING_MPEGH_LC_L3;
+ case AUDIO_FORMAT_MPEGH_LC_L4:
+ return ENCODING_MPEGH_LC_L4;
case AUDIO_FORMAT_DEFAULT:
return ENCODING_DEFAULT;
default:
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 787d348..3acbd1e 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -36,6 +36,7 @@
#include <utils/List.h>
#include <utils/KeyedVector.h>
#include <binder/Parcel.h>
+#include <binder/ParcelRef.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/threads.h>
@@ -515,8 +516,9 @@
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
- Parcel* parcel = new Parcel();
- return reinterpret_cast<jlong>(parcel);
+ sp<ParcelRef> parcelRef = ParcelRef::create();
+ parcelRef->incStrong(reinterpret_cast<const void*>(android_os_Parcel_create));
+ return reinterpret_cast<jlong>(static_cast<Parcel *>(parcelRef.get()));
}
static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -529,8 +531,8 @@
static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr)
{
- Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
- delete parcel;
+ ParcelRef* derivative = static_cast<ParcelRef*>(reinterpret_cast<Parcel*>(nativePtr));
+ derivative->decStrong(reinterpret_cast<const void*>(android_os_Parcel_create));
}
static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 675648a..f7b3f30 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -35,6 +35,7 @@
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
+#include <binder/ParcelRef.h>
#include <binder/ProcessState.h>
#include <binder/Stability.h>
#include <binderthreadstate/CallerUtils.h>
@@ -1367,7 +1368,8 @@
}
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
- jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
+ jint code, jobject dataObj, jobject replyObj, jboolean replyObjOwnsNativeParcel,
+ jint flags) // throws RemoteException
{
if (dataObj == NULL) {
jniThrowNullPointerException(env, NULL);
@@ -1409,6 +1411,21 @@
status_t err = target->transact(code, *data, reply, flags);
//if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
+ if (reply) {
+ if (replyObjOwnsNativeParcel) {
+ // as per Parcel java class constructor, here, "reply" MUST be a "ParcelRef"
+ // only for Parcel that contained Binder objects
+ if (reply->objectsCount() > 0) {
+ IPCThreadState::self()->createTransactionReference(static_cast<ParcelRef*>(reply));
+ }
+ } else {
+ // as per Parcel.java, if Parcel java object NOT owning native Parcel object, it will
+ // NOT destroy the native Parcel object upon GC(finalize()), so, there will be no race
+ // condtion in this case. Please refer to the java class methods: Parcel.finalize(),
+ // Parcel.destroy().
+ }
+ }
+
if (kEnableBinderSample) {
if (time_binder_calls) {
conditionally_log_binder_call(start_millis, target, code);
@@ -1535,7 +1552,7 @@
{"pingBinder", "()Z", (void*)android_os_BinderProxy_pingBinder},
{"isBinderAlive", "()Z", (void*)android_os_BinderProxy_isBinderAlive},
{"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor},
- {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
+ {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;ZI)Z", (void*)android_os_BinderProxy_transact},
{"linkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
{"unlinkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
{"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 4eaa016..9cc7243 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -70,7 +70,8 @@
deviceInfo.isExternal(), deviceInfo.getSources(),
deviceInfo.getKeyboardType(), kcmObj.get(),
deviceInfo.hasVibrator(), hasMic,
- deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor()));
+ deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
+ deviceInfo.hasBattery()));
const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (const InputDeviceInfo::MotionRange& range: ranges) {
@@ -90,9 +91,10 @@
gInputDeviceClassInfo.clazz = FindClassOrDie(env, "android/view/InputDevice");
gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
- gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
- "(IIILjava/lang/String;IILjava/lang/"
- "String;ZIILandroid/view/KeyCharacterMap;ZZZZ)V");
+ gInputDeviceClassInfo.ctor =
+ GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
+ "(IIILjava/lang/String;IILjava/lang/"
+ "String;ZIILandroid/view/KeyCharacterMap;ZZZZZ)V");
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFFF)V");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4ef63ae..05fcaec 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -27,6 +27,7 @@
#include <android-base/chrono_utils.h>
#include <android/graphics/region.h>
#include <android/gui/BnScreenCaptureListener.h>
+#include <android/os/IInputConstants.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <android_runtime/android_view_Surface.h>
@@ -110,10 +111,12 @@
static struct {
jfieldID pixelFormat;
jfieldID sourceCrop;
- jfieldID frameScale;
+ jfieldID frameScaleX;
+ jfieldID frameScaleY;
jfieldID captureSecureLayers;
jfieldID allowProtected;
jfieldID uid;
+ jfieldID grayscale;
} gCaptureArgsClassInfo;
static struct {
@@ -380,13 +383,17 @@
captureArgs.sourceCrop =
rectFromObj(env,
env->GetObjectField(captureArgsObject, gCaptureArgsClassInfo.sourceCrop));
- captureArgs.frameScale =
- env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScale);
+ captureArgs.frameScaleX =
+ env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
+ captureArgs.frameScaleY =
+ env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleY);
captureArgs.captureSecureLayers =
env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers);
captureArgs.allowProtected =
env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.allowProtected);
captureArgs.uid = env->GetLongField(captureArgsObject, gCaptureArgsClassInfo.uid);
+ captureArgs.grayscale =
+ env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.grayscale);
}
static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
@@ -1619,7 +1626,8 @@
jlong frameTimelineVsyncId) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->setFrameTimelineVsync(frameTimelineVsyncId);
+ transaction->setFrameTimelineInfo(
+ {frameTimelineVsyncId, android::os::IInputConstants::INVALID_INPUT_EVENT_ID});
}
class JankDataListenerWrapper : public JankDataListener {
@@ -2032,12 +2040,14 @@
gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I");
gCaptureArgsClassInfo.sourceCrop =
GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;");
- gCaptureArgsClassInfo.frameScale = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScale", "F");
+ gCaptureArgsClassInfo.frameScaleX = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleX", "F");
+ gCaptureArgsClassInfo.frameScaleY = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleY", "F");
gCaptureArgsClassInfo.captureSecureLayers =
GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z");
gCaptureArgsClassInfo.allowProtected =
GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
+ gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
jclass displayCaptureArgsClazz =
FindClassOrDie(env, "android/view/SurfaceControl$DisplayCaptureArgs");
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c76a5d3..3d1c38d 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -70,7 +70,6 @@
#include <bionic/malloc.h>
#include <bionic/mte.h>
#include <cutils/fs.h>
-#include <cutils/memory.h>
#include <cutils/multiuser.h>
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>
@@ -340,6 +339,7 @@
GWP_ASAN_LEVEL_NEVER = 0 << 21,
GWP_ASAN_LEVEL_LOTTERY = 1 << 21,
GWP_ASAN_LEVEL_ALWAYS = 2 << 21,
+ NATIVE_HEAP_ZERO_INIT = 1 << 23,
};
enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -621,13 +621,6 @@
// Set the jemalloc decay time to 1.
mallopt(M_DECAY_TIME, 1);
-
- // Avoid potentially expensive memory mitigations, mostly meant for system
- // processes, in apps. These may cause app compat problems, use more memory,
- // or reduce performance. While it would be nice to have them for apps,
- // we will have to wait until they are proven out, have more efficient
- // hardware, and/or apply them only to new applications.
- process_disable_memory_mitigations();
}
static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
@@ -1785,9 +1778,22 @@
break;
}
mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level);
+
// Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK;
+ // Avoid heap zero initialization for applications without MTE. Zero init may
+ // cause app compat problems, use more memory, or reduce performance. While it
+ // would be nice to have them for apps, we will have to wait until they are
+ // proven out, have more efficient hardware, and/or apply them only to new
+ // applications.
+ if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) {
+ mallopt(M_BIONIC_ZERO_INIT, 0);
+ }
+
+ // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
+ runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT;
+
bool forceEnableGwpAsan = false;
switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
default:
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 748b4b4..99fd215 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -16,6 +16,7 @@
jjaggi@google.com
roosa@google.com
per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com
+per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
# Biometrics
kchyn@google.com
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index e683306..bb39ea8 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -110,6 +110,8 @@
optional int32 network_security_config_res = 17;
optional int32 category = 18;
optional int32 enable_gwp_asan = 19;
+ optional int32 enable_memtag = 20;
+ optional bool native_heap_zero_init = 21;
}
optional Detail detail = 17;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index f5aa17d..632d372 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -518,6 +518,11 @@
}
optional Search search = 48;
+ message CameraAutorotate {
+ optional SettingProto enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional CameraAutorotate camera_autorotate = 88;
+
message SpellChecker {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -642,5 +647,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 88;
+ // Next tag = 89;
}
diff --git a/core/proto/android/server/apphibernationservice.proto b/core/proto/android/server/apphibernationservice.proto
new file mode 100644
index 0000000..d341c4b
--- /dev/null
+++ b/core/proto/android/server/apphibernationservice.proto
@@ -0,0 +1,42 @@
+/*
+ * 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 = "proto2";
+package com.android.server.apphibernation;
+
+option java_multiple_files = true;
+
+// Proto for hibernation states for all packages for a user.
+message UserLevelHibernationStatesProto {
+ repeated UserLevelHibernationStateProto hibernation_state = 1;
+}
+
+// Proto for com.android.server.apphibernation.UserLevelState.
+message UserLevelHibernationStateProto {
+ optional string package_name = 1;
+ optional bool hibernated = 2;
+}
+
+// Proto for global hibernation states for all packages.
+message GlobalLevelHibernationStatesProto {
+ repeated GlobalLevelHibernationStateProto hibernation_state = 1;
+}
+
+// Proto for com.android.server.apphibernation.GlobalLevelState
+message GlobalLevelHibernationStateProto {
+ optional string package_name = 1;
+ optional bool hibernated = 2;
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4acdb16..3bff755 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -93,6 +93,7 @@
<protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
<protected-broadcast android:name="android.intent.action.USER_INITIALIZE" />
<protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.DOMAINS_NEED_VERIFICATION" />
<protected-broadcast android:name="android.intent.action.OVERLAY_ADDED" />
<protected-broadcast android:name="android.intent.action.OVERLAY_CHANGED" />
<protected-broadcast android:name="android.intent.action.OVERLAY_REMOVED" />
@@ -1748,7 +1749,7 @@
@SystemApi
<p>Not for use by third-party applications. @hide -->
<permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM"
- android:protectionLevel="signature|setup" />
+ android:protectionLevel="signature|privileged" />
<!-- @SystemApi @hide Allows applications to toggle airplane mode.
<p>Not for use by third-party or privileged applications.
@@ -2708,13 +2709,13 @@
shown on top of all other apps.
Allows an application to use
- {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY}
+ {@link android.view.WindowManager.LayoutsParams#setSystemApplicationOverlay(boolean)}
to create overlays that will stay visible, even if another window is requesting overlays to
be hidden through {@link android.view.Window#setHideOverlayWindows(boolean)}.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
- android:protectionLevel="signature|wellbeing"/>
+ android:protectionLevel="signature|recents|wellbeing"/>
<!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
@hide
@@ -4133,11 +4134,6 @@
<permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows an application to use background blur.
- @hide -->
- <permission android:name="android.permission.USE_BACKGROUND_BLUR"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows an application to control the lights on the device.
@hide
@SystemApi
@@ -4707,6 +4703,26 @@
<permission android:name="android.permission.BIND_INTENT_FILTER_VERIFIER"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Domain verification agent package needs to have this permission before the
+ system will trust it to verify domains.
+
+ TODO(159952358): STOPSHIP: This must be updated to the new "internal" protectionLevel
+ -->
+ <permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Must be required by the domain verification agent's intent
+ BroadcastReceiver, to ensure that only the system can interact with it.
+ -->
+ <permission android:name="android.permission.BIND_DOMAIN_VERIFICATION_AGENT"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an app like Settings to update the user's grants to what domains
+ an app is allowed to automatically open.
+ -->
+ <permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows applications to access serial ports via the SerialManager.
@hide -->
<permission android:name="android.permission.SERIAL_PORT"
@@ -5168,6 +5184,11 @@
<permission android:name="android.permission.MANAGE_SEARCH_UI"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the smartspace service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SMARTSPACE"
+ android:protectionLevel="signature" />
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -5293,7 +5314,8 @@
android:protectionLevel="signature" />
<!-- Allows financial apps to read filtered sms messages.
- Protection level: signature|appop -->
+ Protection level: signature|appop
+ @deprecated The API that used this permission is no longer functional. -->
<permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
android:protectionLevel="signature|appop" />
@@ -5411,11 +5433,12 @@
<permission android:name="android.permission.CONTROL_DEVICE_STATE"
android:protectionLevel="signature" />
- <!-- Must be required by an {@link android.service.attestation.ImpressionAttestationService}
- to ensure that only the system can bind to it.
- @hide This is not a third-party API (intended for OEMs and system apps).
+ <!-- Must be required by a
+ {@link android.service.screenshot.ScreenshotHasherService}
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
-->
- <permission android:name="android.permission.BIND_IMPRESSION_ATTESTATION_SERVICE"
+ <permission android:name="android.permission.BIND_SCREENSHOT_HASHER_SERVICE"
android:protectionLevel="signature" />
<!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
diff --git a/core/res/OWNERS b/core/res/OWNERS
index a30111b..9d739b9 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -6,9 +6,12 @@
dupin@google.com
hackbod@android.com
hackbod@google.com
+ilyamaty@google.com
+jaggies@google.com
jsharkey@android.com
jsharkey@google.com
juliacr@google.com
+kchyn@google.com
michaelwr@google.com
nandana@google.com
narayan@google.com
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 8746afa..55eaaf6 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Net Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-rugsteunoproepe"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nie aangestuur nie"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondes"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 4daa9e3..3161226 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi ብቻ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ምትኬ ጥሪ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡አልተላለፈም"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡<xliff:g id="DIALING_NUMBER">{1}</xliff:g> ከ<xliff:g id="TIME_DELAY">{2}</xliff:g> ሰከንዶች በኋላ"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 2f937e0..e91e2b7 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnız Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedək Zəng"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönləndirilmədi"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> saniyə sonra"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index b3323b3..a490a6c 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> rezervni način za pozivanje"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunde/i"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 173f9b5..20342e0 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Обаждания през друга SIM карта от <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Не е пренасочено"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> след <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 1c8644e..989e2bb 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – pomoćno pozivanje"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđen"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> za <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index d369134..e9c55d7 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Només Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Trucades alternatives (<xliff:g id="SPN">%s</xliff:g>)"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no s\'ha desviat"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> després de <xliff:g id="TIME_DELAY">{2}</xliff:g> segons"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 7936adc..071bbfd 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Pouze Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Záložní volání"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index ba0ef7f..b760f5b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Kun Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Alternativ løsning til opkald leveret af <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ea56ee2..4f3c8a9 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Μόνο Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Δημιουργία αντιγράφων ασφαλείας κλήσεων"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Δεν προωθήθηκε"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> μετά από <xliff:g id="TIME_DELAY">{2}</xliff:g> δευτερόλεπτα"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 4ee4b8e..c5ff193 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamada de copia de seguridad de <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> después de <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 39f8ef4f..108c3db 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamadas de reserva de <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> transcurridos <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index faf1b74..70862d4 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Ainult WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – helistamise varuviis"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole suunatud"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi pärast"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 9258b30..6a0e454 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"فقط Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> تماس پشتیبان"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: هدایت نشده"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> پس از <xliff:g id="TIME_DELAY">{2}</xliff:g> ثانیه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 034ee0b..8e5d9b4 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Vain Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Puheluiden varavaihtoehto"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ei siirretty"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunnin päästä"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 8418604..eadac9e 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Só por wifi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas de <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: non desviada"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> tras <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index f3cf7aa..5f0a8f8 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"केवल वाई-फ़ाई"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> बैक अप कॉलिंग"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकंड के बाद"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index aac0650..e1ae1e2 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Rezervni način telefoniranja"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđeno"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 841553a..3c04bec 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Csak Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> másodlagos hívási lehetőség"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nincs átirányítva"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> másodperc után"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index cddcfe6..bdd2387 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Միայն Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> զանգելու պահեստային տարբերակ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> վայրկյանից"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 8bd34c3..c84bc13 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Khusus Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Panggilan Cadangan <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak diteruskan"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> setelah <xliff:g id="TIME_DELAY">{2}</xliff:g> detik"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index f9536fe..84611a2 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi eingöngu"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Varasímtöl (<xliff:g id="SPN">%s</xliff:g>)"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ekki áframsent"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> eftir <xliff:g id="TIME_DELAY">{2}</xliff:g> sekúndur"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 409b378..f104d44 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi בלבד"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"אמצעי גיבוי להתקשרות באמצעות <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ללא העברה"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> כעבור <xliff:g id="TIME_DELAY">{2}</xliff:g> שניות"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 0696580..69822ac 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fiのみ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 通話のバックアップ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g> (<xliff:g id="TIME_DELAY">{2}</xliff:g>秒後)"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ae1e6eb..6a7d563 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"მხოლოდ Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> დარეკვის სარეზერვო ხერხი"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: არ არის გადამისამართებული"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> წამის შემდეგ"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index fac878d..d1400b9 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi តែប៉ុណ្ណោះ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"ការហៅទូរសព្ទបម្រុង <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> ៖ មិនបានបញ្ជូនបន្ត"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> បន្ទាប់ពី <xliff:g id="TIME_DELAY">{2}</xliff:g> វិនាទី"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index afeb1bd..32e3c4d 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ವೈ-ಫೈ ಮಾತ್ರ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ಬ್ಯಾಕಪ್ ಕರೆ ಮಾಡುವಿಕೆ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗಿಲ್ಲ"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> ಸೆಕೆಂಡುಗಳ ನಂತರ <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index acc5a6a..3407431 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi에서만"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 백업 전화"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: 착신전환 안됨"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g><xliff:g id="TIME_DELAY">{2}</xliff:g>초 후"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index d6ff927..9a84d0a 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi гана"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Кошумча чалуу ыкмасы"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Багытталган эмес"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секунддан кийин"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 5c65d06..5f72e07 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tik „Wi-Fi“"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – atsarginis skambinimas"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neperadresuota"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 2840429..37e417f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tikai Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: zvanu rezerves iespēja"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nav pāradresēts"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pēc <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundes(-ēm)"</string>
diff --git a/core/res/res/values-mcc310-mnc950-si/strings.xml b/core/res/res/values-mcc310-mnc950-si/strings.xml
deleted file mode 100644
index 26fe4ac..0000000
--- a/core/res/res/values-mcc310-mnc950-si/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/* //device/apps/common/assets/res/any/strings.xml
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="615419724607901560">"SIM MM#2 ප්රතිපාදනය නොකරයි"</string>
- <string name="mmcc_illegal_ms" msgid="7801541624846497489">"SIM MM#3 ඉඩ නොදේ"</string>
- <string name="mmcc_illegal_me" msgid="7066936962628406316">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
-</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 3466bca..5666cc2 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервен начин на повикување преку <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е препратено"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> по <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 2c44adc..891ac47 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Зөвхөн Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Дуудлагыг нөөцлөх"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: дамжуулагдаагүй"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секундын дараа"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 128675e..ec0aa5d 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi sahaja"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Panggilan Sandaran"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak dimajukan"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> selepas <xliff:g id="TIME_DELAY">{2}</xliff:g> saat"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 62d2d85..7b4a430fc 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ကြိုးမဲ့အင်တာနက် သာလျှင်"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> အရန် ခေါ်ဆိုမှု"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ထပ်ဆင့်မပို့နိုင်ပါ"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> နောက် <xliff:g id="TIME_DELAY">{2}</xliff:g> စက္ကန့်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b20111e..4968184 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-reserve for anrop"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> etter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 9ee5e19..f73b382 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tylko Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Zapasowa metoda wykonywania połączeń <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundach"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 0663ec7..693af83 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index ccb7a64..e00034a 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Apenas Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Fazer uma cópia de segurança das chamadas <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não reencaminhado"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 0663ec7..693af83 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index e08cb57..5fd6f1a 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Numai Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Apelare de rezervă prin <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neredirecționată"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> după <xliff:g id="TIME_DELAY">{2}</xliff:g> secunde"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index f800195..d1cd374 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Только Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервный способ связи: <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переадресовано"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> через <xliff:g id="TIME_DELAY">{2}</xliff:g> с."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 322c03c..d102183 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi පමණයි"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> උපස්ථ ඇමතුම"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ඉදිරියට නොයවන ලදි"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: තත්පර <xliff:g id="TIME_DELAY">{2}</xliff:g> ට පසුව <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 7172cab..ffa4b26 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Len Wi‑Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – záložné volanie"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepresmerované"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index af03b61..65037a0 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> резервни начин за позивање"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Није прослеђено"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> након <xliff:g id="TIME_DELAY">{2}</xliff:g> секунде/и"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index b87e12b..ef7a102 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Endast Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> används som reserv för samtal"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Vidarebefordras inte"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 397a3b8..cc82f4e 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi pekee"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>Kupiga Simu Kupitia Mtandao Mbadala"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Haijatumiwa mwingine"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> baada ya sekunde <xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 3c24672..ca90171 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"வைஃபை மட்டும்"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> காப்புப் பிரதி அழைப்பு"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: பகிரப்படவில்லை"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> வினாடிகளுக்குப் பிறகு <xliff:g id="DIALING_NUMBER">{1}</xliff:g> ஐப் பகிர்"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 6012c0a2..5079f23 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi เท่านั้น"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"การสำรองข้อมูลการโทรจาก <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ไม่ได้โอนสาย"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> หลังผ่านไป <xliff:g id="TIME_DELAY">{2}</xliff:g> วินาที"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 9e677c3..81bfe52 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi lang"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup na Pagtawag"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Hindi naipasa"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pagkatapos ng <xliff:g id="TIME_DELAY">{2}</xliff:g> (na) segundo"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index fb23a29..7095bb8 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnızca kablosuz"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedek Arama"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönlendirilmedi"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> saniye sonra <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index ecfcb1b..9864b4b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Лише Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: резервний спосіб здійснення викликів"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переслано"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> після <xliff:g id="TIME_DELAY">{2}</xliff:g> сек."</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 0b48dc1..6612cc9 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Faqat Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Zaxiraviy chaqiruv"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yo‘naltirilmadi"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> soniyadan so‘ng"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 438befd..8f8e0bc 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Chỉ Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Gọi điện dự phòng <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> sau <xliff:g id="TIME_DELAY">{2}</xliff:g> giây"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 20048f1..af5f846 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"仅限 WLAN"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>备用通话"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g>秒后<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 544cf1e..65586f7 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:尚未轉接"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> 於 <xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後轉接"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index d9b51e0..6facceb 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未轉接"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後 <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 029c569..3b22e94 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"I-Wi-Fi kuphela"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Ukushaya Ikholi Kwekhopi Yasenqolobaneni"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Akudlulisiwe"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> emuva kwamasekhondi angu-<xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 14f1e0e..eb86709 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -87,14 +87,12 @@
theme does not set this value, meaning it is based on whether the
window is floating. -->
<attr name="backgroundDimEnabled" format="boolean" />
- <!-- When windowBackgroundBlurEnabled is set, this is the amount of blur to apply
- behind the window. The range is from 0, which means no blur, to 150.
- @hide @SystemApi -->
- <attr name="windowBackgroundBlurRadius" format="dimension"/>
- <!-- If set, the area behind the window will be blurred with radius
- windowBackgroundBlurRadius.
- @hide @SystemApi -->
- <attr name="windowBackgroundBlurEnabled" format="boolean" />
+ <!-- When windowBlurBehindEnabled is set, this is the amount of blur to apply
+ behind the window. The range is from 0, which means no blur, to 150. -->
+ <attr name="windowBlurBehindRadius" format="dimension"/>
+ <!-- If set, everything behind the window will be blurred with radius
+ windowBackgroundBlurRadius. -->
+ <attr name="windowBlurBehindEnabled" format="boolean" />
<!-- Color of background imagery used for popup windows. -->
@@ -1974,8 +1972,8 @@
<attr name="textColor" />
<attr name="backgroundDimEnabled" />
<attr name="backgroundDimAmount" />
- <attr name="windowBackgroundBlurEnabled" />
- <attr name="windowBackgroundBlurRadius" />
+ <attr name="windowBlurBehindEnabled" />
+ <attr name="windowBlurBehindRadius" />
<attr name="windowActionBar" />
<attr name="windowActionModeOverlay" />
<attr name="windowActionBarOverlay" />
@@ -4069,6 +4067,13 @@
<attr name="dial" format="reference"/>
<attr name="hand_hour" format="reference"/>
<attr name="hand_minute" format="reference"/>
+ <attr name="hand_second" format="reference"/>
+ <!-- Specifies the time zone to use. When this attribute is specified, the
+ TextClock will ignore the time zone of the system. To use the user's
+ time zone, do not specify this attribute. The default value is the
+ user's time zone. Please refer to {@link java.util.TimeZone} for more
+ information about time zone ids. -->
+ <attr name="timeZone" format="string"/>
</declare-styleable>
<declare-styleable name="Button">
</declare-styleable>
@@ -8435,6 +8440,9 @@
<attr name="errorColor" format="color|reference" />
<!-- The success color -->
<attr name="successColor" format="color|reference"/>
+ <!-- The dot color -->
+ <attr name="dotColor" format="color|reference"/>
+
</declare-styleable>
<!-- =============================== -->
@@ -9193,6 +9201,9 @@
<!-- @hide From Theme.colorBackground, used for the TaskDescription background
color. -->
<attr name="colorBackground" />
+ <!-- @hide From Theme.colorBackgroundFloating, used for the TaskDescription background
+ color floating. -->
+ <attr name="colorBackgroundFloating" />
<!-- @hide From Theme.statusBarColor, used for the TaskDescription status bar color. -->
<attr name="statusBarColor"/>
<!-- @hide From Theme.navigationBarColor, used for the TaskDescription navigation bar
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c16588c..b4e580a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1580,6 +1580,13 @@
<enum name="always" value="1" />
</attr>
+ <attr name="memtagMode">
+ <enum name="default" value="-1" />
+ <enum name="off" value="0" />
+ <enum name="async" value="1" />
+ <enum name="sync" value="2" />
+ </attr>
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1847,6 +1854,10 @@
<attr name="gwpAsanMode" />
+ <attr name="memtagMode" />
+
+ <attr name="nativeHeapZeroInit" format="boolean" />
+
<!-- @hide no longer used, kept to preserve padding -->
<attr name="allowAutoRevokePermissionsExemption" format="boolean" />
@@ -2417,6 +2428,8 @@
<!-- Required name of the process that is allowed -->
<attr name="process" />
<attr name="gwpAsanMode" />
+ <attr name="memtagMode" />
+ <attr name="nativeHeapZeroInit" />
</declare-styleable>
<!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 7ca4f4c..c8cccc4 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -17,9 +17,9 @@
<!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
overlaying new theme colors. -->
<resources>
- <color name="primary_device_default_dark">@color/system_main_900</color>
+ <color name="primary_device_default_dark">@color/system_main_800</color>
<color name="primary_device_default_light">@color/system_main_50</color>
- <color name="primary_device_default_settings">@color/system_main_900</color>
+ <color name="primary_device_default_settings">@color/system_main_800</color>
<color name="primary_device_default_settings_light">@color/primary_device_default_light</color>
<color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color>
<color name="primary_dark_device_default_light">@color/primary_device_default_light</color>
@@ -37,9 +37,9 @@
<color name="accent_device_default_dark">@color/system_accent_200</color>
<color name="accent_device_default">@color/accent_device_default_light</color>
- <color name="background_device_default_dark">@color/system_main_900</color>
+ <color name="background_device_default_dark">@color/system_main_800</color>
<color name="background_device_default_light">@color/system_main_50</color>
- <color name="background_floating_device_default_dark">@color/system_main_800</color>
+ <color name="background_floating_device_default_dark">@color/system_main_900</color>
<color name="background_floating_device_default_light">@color/system_main_100</color>
<color name="text_color_primary_device_default_light">@color/system_main_900</color>
@@ -53,8 +53,8 @@
<color name="error_color_device_default_dark">@color/error_color_material_dark</color>
<color name="error_color_device_default_light">@color/error_color_material_light</color>
- <color name="list_divider_color_light">#ffdadce0</color>
- <color name="list_divider_color_dark">#85ffffff</color>
+ <color name="list_divider_color_light">@color/system_main_500</color>
+ <color name="list_divider_color_dark">@color/system_main_400</color>
<color name="list_divider_opacity_device_default_light">@android:color/white</color>
<color name="list_divider_opacity_device_default_dark">@android:color/white</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5e0cda6..3790aa4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -517,6 +517,9 @@
<!-- Flag indicating whether we should enable smart battery. -->
<bool name="config_smart_battery_available">false</bool>
+ <!-- Flag indicating whether we should enable camera-based autorotate -->
+ <bool name="config_camera_autorotate">false</bool>
+
<!-- Fast brightness animation ramp rate in brightness units per second-->
<integer translatable="false" name="config_brightness_ramp_rate_fast">180</integer>
@@ -660,9 +663,15 @@
The default is false. -->
<bool name="config_lidControlsSleep">false</bool>
- <!-- The device state (supplied by DeviceStateManager) that should be treated as folded by the
- display fold controller. Default is DeviceStateManager.INVALID_DEVICE_STATE. -->
- <integer name="config_foldedDeviceState">-1</integer>
+ <!-- The device states (supplied by DeviceStateManager) that should be treated as folded by the
+ display fold controller. Default is empty. -->
+ <integer-array name="config_foldedDeviceStates">
+ <!-- Example:
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ -->
+ </integer-array>
<!-- Indicate the display area rect for foldable devices in folded state. -->
<string name="config_foldedArea"></string>
@@ -822,28 +831,21 @@
<!-- B y-intercept --> <item>-0.198650895</item>
</string-array>
- <string-array name="config_reduceBrightColorsCoefficientsNative">
- <!-- R a-coefficient --> <item>-0.691218457</item>
- <!-- R b-coefficient --> <item>0.050135153</item>
- <!-- R y-intercept --> <item>0.917684143</item>
- <!-- G a-coefficient --> <item>-0.691218457</item>
- <!-- G b-coefficient --> <item>0.050135153</item>
- <!-- G y-intercept --> <item>0.917684143</item>
- <!-- B a-coefficient --> <item>-0.691218457</item>
- <!-- B b-coefficient --> <item>0.050135153</item>
- <!-- B y-intercept --> <item>0.917684143</item>
+ <!-- Control whether bright color reduction is available. This should only be enabled on devices
+ that have a HWC implementation that can apply the matrix passed to setColorTransform
+ without impacting power, performance, and app compatibility (e.g. protected content). -->
+ <bool name="config_reduceBrightColorsAvailable">@bool/config_setColorTransformAccelerated</bool>
+
+ <string-array name="config_reduceBrightColorsCoefficientsNonlinear">
+ <!-- a-coefficient --> <item>-0.4429953456</item>
+ <!-- b-coefficient --> <item>-0.2434077725</item>
+ <!-- y-intercept --> <item>0.9809063061</item>
</string-array>
<string-array name="config_reduceBrightColorsCoefficients">
- <!-- R a-coefficient --> <item>0.00000000000000154</item>
- <!-- R b-coefficient --> <item>-1.0</item>
- <!-- R y-intercept --> <item>1.045977011</item>
- <!-- G a-coefficient --> <item>0.00000000000000224</item>
- <!-- G b-coefficient --> <item>-1.0</item>
- <!-- G y-intercept --> <item>1.045977011</item>
- <!-- B a-coefficient --> <item>0.0000000000000022</item>
- <!-- B b-coefficient --> <item>-1.0</item>
- <!-- B y-intercept --> <item>1.045977011</item>
+ <!-- a-coefficient --> <item>-0.000000000000001</item>
+ <!-- b-coefficient --> <item>-0.955555555555554</item>
+ <!-- y-intercept --> <item>1.000000000000000</item>
</string-array>
<!-- Boolean indicating whether display white balance is supported. -->
@@ -3800,6 +3802,15 @@
-->
<string name="config_defaultSearchUiService" translatable="false"></string>
+ <!-- The package name for the system's smartspace service.
+ This service returns smartspace results.
+
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, smartspace will be disabled.
+ Example: "com.android.intelligence/.SmartspaceService"
+-->
+ <string name="config_defaultSmartspaceService" translatable="false"></string>
+
<!-- The package name for the system's speech recognition service.
This service must be trusted, as it can be activated without explicit consent of the user.
Example: "com.android.speech/.RecognitionService"
@@ -4395,6 +4406,15 @@
<!--If true, allows the device to load udfps components on older HIDL implementations -->
<bool name="allow_test_udfps" translatable="false" >false</bool>
+ <!-- The properties of a UDFPS sensor in pixels, in the order listed below: -->
+ <integer-array name="config_udfps_sensor_props" translatable="false" >
+ <!--
+ <item>sensorLocationX</item>
+ <item>sensorLocationY</item>
+ <item>sensorRadius</item>
+ -->
+ </integer-array>
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
@@ -4636,6 +4656,23 @@
corners of the activity won't be rounded. -->
<integer name="config_letterboxActivityCornersRadius">0</integer>
+ <!-- Corners appearance of the letterbox background.
+ 0 - Solid background using color specified in R.color.config_letterboxBackgroundColor.
+ 1 - Color specified in R.attr.colorBackground for the letterboxed application.
+ 2 - Color specified in R.attr.colorBackgroundFloating for the letterboxed application.
+ If given value is outside of this range, the option 0 will be assummed. -->
+ <integer name="config_letterboxBackgroundType">0</integer>
+
+ <!-- Color of the letterbox background if one following conditions is true
+ - Option 0 is selected for R.integer.config_letterboxBackgroundType.
+ - Option 1 is selected for R.integer.config_letterboxBackgroundType and
+ R.attr.colorBackground isn't specified for the app.
+ - Option 2 is selected for R.integer.config_letterboxBackgroundType and
+ R.attr.colorBackgroundFloating isn't specified for the app.
+ Defaults to black if not specified.
+ -->
+ <color name="config_letterboxBackgroundColor">#000</color>
+
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
@@ -4670,4 +4707,7 @@
<!-- 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>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 3ae2131..cb16af5 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -622,9 +622,9 @@
aliasing effects). This is only used on circular displays. -->
<dimen name="circular_display_mask_thickness">1px</dimen>
- <dimen name="lock_pattern_dot_line_width">3dp</dimen>
- <dimen name="lock_pattern_dot_size">12dp</dimen>
- <dimen name="lock_pattern_dot_size_activated">28dp</dimen>
+ <dimen name="lock_pattern_dot_line_width">22dp</dimen>
+ <dimen name="lock_pattern_dot_size">14dp</dimen>
+ <dimen name="lock_pattern_dot_size_activated">30dp</dimen>
<dimen name="text_handle_min_size">40dp</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9c1c51c..a7e8f2a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3048,17 +3048,17 @@
<public name="allowClickWhenDisabled" />
<public name="windowLayoutAffinity" />
<public name="canPauseRecording" />
- <!-- @hide -->
- <!-- @hide @SystemApi -->
- <public name="windowBackgroundBlurRadius"/>
- <!-- @hide @SystemApi -->
- <public name="windowBackgroundBlurEnabled"/>
+ <public name="windowBlurBehindRadius"/>
+ <public name="windowBlurBehindEnabled"/>
<public name="requireDeviceScreenOn" />
<public name="pathSuffix" />
<public name="sspSuffix" />
<public name="pathAdvancedPattern" />
<public name="sspAdvancedPattern" />
<public name="fontProviderSystemFontFamily" />
+ <public name="hand_second" />
+ <public name="memtagMode" />
+ <public name="nativeHeapZeroInit" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dfccdf4..80163b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1881,6 +1881,7 @@
<java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" />
<java-symbol type="bool" name="config_useAttentionLight" />
<java-symbol type="bool" name="config_adaptive_sleep_available" />
+ <java-symbol type="bool" name="config_camera_autorotate"/>
<java-symbol type="bool" name="config_animateScreenLights" />
<java-symbol type="bool" name="config_automatic_brightness_available" />
<java-symbol type="bool" name="config_smart_battery_available" />
@@ -2540,6 +2541,7 @@
<java-symbol type="string" name="config_biometric_prompt_ui_package" />
<java-symbol type="array" name="config_biometric_sensors" />
<java-symbol type="bool" name="allow_test_udfps" />
+ <java-symbol type="array" name="config_udfps_sensor_props" />
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -3191,8 +3193,9 @@
<java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
<java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
<java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
+ <java-symbol type="bool" name="config_reduceBrightColorsAvailable" />
<java-symbol type="array" name="config_reduceBrightColorsCoefficients" />
- <java-symbol type="array" name="config_reduceBrightColorsCoefficientsNative" />
+ <java-symbol type="array" name="config_reduceBrightColorsCoefficientsNonlinear" />
<java-symbol type="array" name="config_availableColorModes" />
<java-symbol type="array" name="config_mappedColorModes" />
<java-symbol type="string" name="config_vendorColorModesRestoreHint" />
@@ -3498,6 +3501,7 @@
<java-symbol type="string" name="config_defaultAppPredictionService" />
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="config_defaultSearchUiService" />
+ <java-symbol type="string" name="config_defaultSmartspaceService" />
<java-symbol type="string" name="config_defaultMusicRecognitionService" />
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultRotationResolverService" />
@@ -3728,7 +3732,7 @@
<java-symbol type="string" name="config_customCountryDetector" />
<!-- For Foldables -->
- <java-symbol type="integer" name="config_foldedDeviceState" />
+ <java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="string" name="config_foldedArea" />
<java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
@@ -4133,6 +4137,8 @@
<java-symbol type="dimen" name="config_taskLetterboxAspectRatio" />
<java-symbol type="integer" name="config_letterboxActivityCornersRadius" />
+ <java-symbol type="integer" name="config_letterboxBackgroundType" />
+ <java-symbol type="color" name="config_letterboxBackgroundColor" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
@@ -4169,4 +4175,6 @@
<java-symbol type="bool" name="config_telephony5gNonStandalone" />
<java-symbol type="bool" name="config_voice_data_sms_auto_fallback" />
+
+ <java-symbol type="bool" name="config_enableOneHandedKeyguard" />
</resources>
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index da6636b..6c28607 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.graphics.gamemanagertests"
+ package="com.android.app.gamemanagertests"
android:sharedUserId="android.uid.system" >
<application>
@@ -24,7 +24,7 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.graphics.gamemanagertests"
+ android:targetPackage="com.android.app.gamemanagertests"
android:label="Game Manager Tests"/>
</manifest>
diff --git a/core/tests/GameManagerTests/AndroidTest.xml b/core/tests/GameManagerTests/AndroidTest.xml
index bfb9802..43729923 100644
--- a/core/tests/GameManagerTests/AndroidTest.xml
+++ b/core/tests/GameManagerTests/AndroidTest.xml
@@ -25,7 +25,7 @@
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.graphics.gamemanagertests" />
+ <option name="package" value="com.android.app.gamemanagertests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false" />
</test>
diff --git a/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
similarity index 95%
rename from core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java
rename to core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index d861a89..8f50051 100644
--- a/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package android.graphics;
+package android.app;
import static junit.framework.Assert.assertEquals;
-import android.graphics.GameManager.GameMode;
+import android.app.GameManager.GameMode;
import android.util.ArrayMap;
import android.util.Pair;
@@ -31,7 +31,7 @@
import org.junit.runner.RunWith;
/**
- * Unit tests for {@link GameManager}.
+ * Unit tests for {@link android.app.GameManager}.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index e0d159b..9e6827c 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -63,7 +63,9 @@
private final List<Entry> mEntries = new ArrayList<>();
public BatteryConsumerData(Context context, BatteryStatsHelper batteryStatsHelper,
- BatteryUsageStats batteryUsageStats, String batteryConsumerId) {
+ List<BatteryUsageStats> batteryUsageStatsList, String batteryConsumerId) {
+ BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
+ BatteryUsageStats powerProfileModeledUsageStats = batteryUsageStatsList.get(1);
List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
BatteryStats batteryStats = batteryStatsHelper.getStats();
@@ -142,16 +144,22 @@
}
BatteryConsumer requestedBatteryConsumer = null;
- double totalModeledCpuPowerMah = 0;
for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
requestedBatteryConsumer = consumer;
}
+ }
- totalModeledCpuPowerMah += consumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- + BatteryConsumer.POWER_COMPONENT_CPU);
+ double totalModeledCpuPowerMah = 0;
+ BatteryConsumer requestedBatteryConsumerPowerProfileModeled = null;
+ for (BatteryConsumer consumer : powerProfileModeledUsageStats.getUidBatteryConsumers()) {
+ if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
+ requestedBatteryConsumerPowerProfileModeled = consumer;
+ }
+
+ totalModeledCpuPowerMah += consumer.getConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU);
}
if (requestedBatterySipper == null) {
@@ -196,11 +204,10 @@
addEntry("CPU", EntryType.POWER,
requestedBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU),
totalCpuPowerMah);
- if (totalModeledCpuPowerMah != 0) {
+ if (requestedBatteryConsumerPowerProfileModeled != null) {
addEntry("CPU (modeled)", EntryType.POWER,
- requestedBatteryConsumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- + BatteryConsumer.POWER_COMPONENT_CPU),
+ requestedBatteryConsumerPowerProfileModeled.getConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU),
totalModeledCpuPowerMah);
}
} else {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index 87a175a..4ead8ee 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -68,7 +68,7 @@
private ActivityResultLauncher<Void> mStartAppPicker = registerForActivityResult(
BatteryConsumerPickerActivity.CONTRACT, this::onApplicationSelected);
private BatteryStatsHelper mBatteryStatsHelper;
- private BatteryUsageStats mBatteryUsageStats;
+ private List<BatteryUsageStats> mBatteryUsageStats;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -188,7 +188,8 @@
}
}
- private static class BatteryUsageStatsLoader extends AsyncLoaderCompat<BatteryUsageStats> {
+ private static class BatteryUsageStatsLoader extends
+ AsyncLoaderCompat<List<BatteryUsageStats>> {
private final BatteryStatsManager mBatteryStatsManager;
BatteryUsageStatsLoader(Context context) {
@@ -197,33 +198,38 @@
}
@Override
- public BatteryUsageStats loadInBackground() {
- final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
- .includeModeled()
- .build();
- return mBatteryStatsManager.getBatteryUsageStats(query);
+ public List<BatteryUsageStats> loadInBackground() {
+ final BatteryUsageStatsQuery queryDefault =
+ new BatteryUsageStatsQuery.Builder().build();
+ final BatteryUsageStatsQuery queryPowerProfileModeledOnly =
+ new BatteryUsageStatsQuery.Builder()
+ .powerProfileModeledOnly()
+ .build();
+ return mBatteryStatsManager.getBatteryUsageStats(
+ List.of(queryDefault, queryPowerProfileModeledOnly));
}
@Override
- protected void onDiscardResult(BatteryUsageStats result) {
+ protected void onDiscardResult(List<BatteryUsageStats> result) {
}
}
- private class BatteryUsageStatsLoaderCallbacks implements LoaderCallbacks<BatteryUsageStats> {
+ private class BatteryUsageStatsLoaderCallbacks
+ implements LoaderCallbacks<List<BatteryUsageStats>> {
@NonNull
@Override
- public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) {
+ public Loader<List<BatteryUsageStats>> onCreateLoader(int id, Bundle args) {
return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this);
}
@Override
- public void onLoadFinished(@NonNull Loader<BatteryUsageStats> loader,
- BatteryUsageStats batteryUsageStats) {
+ public void onLoadFinished(@NonNull Loader<List<BatteryUsageStats>> loader,
+ List<BatteryUsageStats> batteryUsageStats) {
onBatteryUsageStatsLoaded(batteryUsageStats);
}
@Override
- public void onLoaderReset(@NonNull Loader<BatteryUsageStats> loader) {
+ public void onLoaderReset(@NonNull Loader<List<BatteryUsageStats>> loader) {
}
}
@@ -232,7 +238,7 @@
onBatteryStatsDataLoaded();
}
- private void onBatteryUsageStatsLoaded(BatteryUsageStats batteryUsageStats) {
+ private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) {
mBatteryUsageStats = batteryUsageStats;
onBatteryStatsDataLoaded();
}
diff --git a/core/tests/coretests/assets/fonts/OWNERS b/core/tests/coretests/assets/fonts/OWNERS
new file mode 100644
index 0000000..b0e0b9d
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/text/OWNERS
diff --git a/core/tests/coretests/assets/fonts_for_family_selection/OWNERS b/core/tests/coretests/assets/fonts_for_family_selection/OWNERS
new file mode 100644
index 0000000..b0e0b9d
--- /dev/null
+++ b/core/tests/coretests/assets/fonts_for_family_selection/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/text/OWNERS
diff --git a/core/tests/coretests/res/font/OWNERS b/core/tests/coretests/res/font/OWNERS
new file mode 100644
index 0000000..b0e0b9d
--- /dev/null
+++ b/core/tests/coretests/res/font/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/text/OWNERS
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 0c351d1..81eb213 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -132,7 +132,8 @@
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
10, // minWidth
- 20 // minHeight
+ 20, // minHeight
+ 0 // colorBackgroundFloating
);
TaskDescription td2 = new TaskDescription();
@@ -155,7 +156,8 @@
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
10, // minWidth
- 20 // minHeight
+ 20, // minHeight
+ 0 // colorBackgroundFloating
);
TaskDescription td2 = new TaskDescription(
@@ -169,7 +171,8 @@
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
102, // minWidth
- 202 // minHeight
+ 202, // minHeight
+ 0 // colorBackgroundFloating
);
// Must overwrite all public and hidden fields, since other has all fields set.
@@ -198,7 +201,8 @@
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
10, // minWidth
- 20 // minHeight
+ 20, // minHeight
+ 0 // colorBackgroundFloating
);
// Normal parceling should keep everything the same.
@@ -219,7 +223,8 @@
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
10, // minWidth
- 20 // minHeight
+ 20, // minHeight
+ 0 // colorBackgroundFloating
);
// Recycled bitmap will be ignored while parceling.
tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapRecycled));
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
new file mode 100644
index 0000000..af77b6c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+
+public class GenericDocumentTest {
+ @Test
+ public void testRecreateFromParcel() {
+ GenericDocument inDoc =
+ new GenericDocument.Builder<>("uri1", "schema1")
+ .setScore(42)
+ .setPropertyString("propString", "Hello")
+ .setPropertyBytes("propBytes", new byte[][] {{1, 2}})
+ .setPropertyDocument(
+ "propDocument",
+ new GenericDocument.Builder<>("uri2", "schema2")
+ .setPropertyString("propString", "Goodbye")
+ .setPropertyBytes("propBytes", new byte[][] {{3, 4}})
+ .build())
+ .build();
+
+ // Serialize the document
+ Parcel inParcel = Parcel.obtain();
+ inParcel.writeBundle(inDoc.getBundle());
+ byte[] data = inParcel.marshall();
+ inParcel.recycle();
+
+ // Deserialize the document
+ Parcel outParcel = Parcel.obtain();
+ outParcel.unmarshall(data, 0, data.length);
+ outParcel.setDataPosition(0);
+ Bundle outBundle = outParcel.readBundle();
+ outParcel.recycle();
+
+ // Compare results
+ GenericDocument outDoc = new GenericDocument(outBundle);
+ assertThat(inDoc).isEqualTo(outDoc);
+ assertThat(outDoc.getPropertyString("propString")).isEqualTo("Hello");
+ assertThat(outDoc.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][] {{1, 2}});
+ assertThat(outDoc.getPropertyDocument("propDocument").getPropertyString("propString"))
+ .isEqualTo("Goodbye");
+ assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
+ .isEqualTo(new byte[][] {{3, 4}});
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
index 986079f..7d175d9 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
@@ -34,9 +34,9 @@
new AppSearchEmail.Builder("test1").build(),
new AppSearchEmail.Builder("test2").build());
PutDocumentsRequest request =
- new PutDocumentsRequest.Builder().addGenericDocument(emails).build();
+ new PutDocumentsRequest.Builder().addGenericDocuments(emails).build();
- assertThat(request.getDocuments().get(0).getUri()).isEqualTo("test1");
- assertThat(request.getDocuments().get(1).getUri()).isEqualTo("test2");
+ assertThat(request.getGenericDocuments().get(0).getUri()).isEqualTo("test1");
+ assertThat(request.getGenericDocuments().get(1).getUri()).isEqualTo("test2");
}
}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
index c8cee85..0fe44630 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
@@ -33,8 +33,8 @@
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
- .addNamespace("namespace1", "namespace2")
- .addSchemaType("schemaTypes1", "schemaTypes2")
+ .addFilterNamespaces("namespace1", "namespace2")
+ .addFilterSchemas("schemaTypes1", "schemaTypes2")
.addFilterPackageNames("package1", "package2")
.setSnippetCount(5)
.setSnippetCountPerProperty(10)
@@ -49,7 +49,7 @@
.isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD))
.containsExactly("namespace1", "namespace2");
- assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD))
+ assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_FIELD))
.containsExactly("schemaTypes1", "schemaTypes2");
assertThat(bundle.getStringArrayList(SearchSpec.PACKAGE_NAME_FIELD))
.containsExactly("package1", "package2");
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
index aab9229..bf6b07f 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
@@ -75,12 +75,12 @@
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
// By default, the schema is visible.
- SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
+ SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
request =
new SetSchemaRequest.Builder()
- .addSchema(schema)
+ .addSchemas(schema)
.setSchemaTypeVisibilityForSystemUi("Schema", true)
.build();
assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
@@ -91,7 +91,7 @@
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
SetSchemaRequest request =
new SetSchemaRequest.Builder()
- .addSchema(schema)
+ .addSchemas(schema)
.setSchemaTypeVisibilityForSystemUi("Schema", false)
.build();
assertThat(request.getSchemasNotVisibleToSystemUi()).containsExactly("Schema");
@@ -102,7 +102,7 @@
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
// By default, the schema is not visible.
- SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
+ SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
assertThat(request.getSchemasVisibleToPackages()).isEmpty();
PackageIdentifier packageIdentifier =
@@ -112,7 +112,7 @@
request =
new SetSchemaRequest.Builder()
- .addSchema(schema)
+ .addSchemas(schema)
.setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier)
.build();
@@ -126,7 +126,7 @@
SetSchemaRequest request =
new SetSchemaRequest.Builder()
- .addSchema(schema)
+ .addSchemas(schema)
.setSchemaTypeVisibilityForPackage(
"Schema",
/*visible=*/ false,
@@ -147,7 +147,7 @@
SetSchemaRequest request =
new SetSchemaRequest.Builder()
- .addSchema(schema)
+ .addSchemas(schema)
// Set it visible for "Schema"
.setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier)
@@ -165,7 +165,7 @@
SetSchemaRequest request =
new SetSchemaRequest.Builder()
- .addSchema(schema)
+ .addSchemas(schema)
// First set it as visible
.setSchemaTypeVisibilityForPackage(
"Schema",
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 88faa0a..57c0be5 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -78,6 +78,7 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -334,7 +335,8 @@
private ParsingPackage parsePackage(Uri packageURI) {
final String archiveFilePath = packageURI.getPath();
ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(
- new File(archiveFilePath), 0 /*flags*/, false /*collectCertificates*/);
+ new File(archiveFilePath), 0 /*flags*/, Collections.emptyList(),
+ false /*collectCertificates*/);
if (result.isError()) {
throw new IllegalStateException(result.getErrorMessage(), result.getException());
}
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
index b319886..341ee37 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
@@ -149,7 +149,7 @@
0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */,
0 /* sources */, 0 /* keyboardType */, null /* keyCharacterMap */,
false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */,
- true /* hasSensor */);
+ true /* hasSensor */, false /* hasBattery */);
assertTrue(d.hasSensor());
return d;
}
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 6955ca8..564103e 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -117,6 +117,61 @@
}
@Test
+ public void testDurationMono() {
+ assertEquals(1, CombinedVibrationEffect.createSynced(
+ VibrationEffect.createOneShot(1, 1)).getDuration());
+ assertEquals(-1, CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration());
+ assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.createSynced(
+ VibrationEffect.createWaveform(
+ new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0)).getDuration());
+ }
+
+ @Test
+ public void testDurationStereo() {
+ assertEquals(6, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(1, 1))
+ .addVibrator(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(-1, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+ .combine()
+ .getDuration());
+ }
+
+ @Test
+ public void testDurationSequential() {
+ assertEquals(26, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.createOneShot(10, 10), 10)
+ .addNext(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(-1, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+ .combine()
+ .getDuration());
+ }
+
+ @Test
public void testSerializationMono() {
CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
diff --git a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
new file mode 100644
index 0000000..be38260
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.provider.SimPhonebookContract.SimRecords.NameValidationResult;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SimPhonebookContractTest {
+
+ @Test
+ public void getContentUri_invalidEfType_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getContentUri(1, 100)
+ );
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getContentUri(1, -1)
+ );
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getContentUri(1,
+ SimPhonebookContract.ElementaryFiles.EF_UNKNOWN)
+ );
+ }
+
+ @Test
+ public void getItemUri_invalidEfType_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getItemUri(1, 100, 1)
+ );
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getItemUri(1, -1, 1)
+ );
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getItemUri(1,
+ SimPhonebookContract.ElementaryFiles.EF_UNKNOWN, 1)
+ );
+ }
+
+ @Test
+ public void getItemUri_invalidRecordIndex_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getItemUri(1,
+ SimPhonebookContract.ElementaryFiles.EF_ADN, 0)
+ );
+ assertThrows(IllegalArgumentException.class, () ->
+ SimPhonebookContract.SimRecords.getItemUri(1,
+ SimPhonebookContract.ElementaryFiles.EF_ADN, -1)
+ );
+ }
+
+ @Test
+ public void nameValidationResult_isValid_validNames() {
+ assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue();
+ assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue();
+ assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue();
+ assertThat(
+ new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue();
+ }
+
+ @Test
+ public void nameValidationResult_isValid_invalidNames() {
+ assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse();
+ assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse();
+ NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c",
+ "A b c", 5, 5);
+ assertThat(unsupportedCharactersResult.isValid()).isFalse();
+ assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue();
+ assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse();
+ assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue();
+ assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse();
+ assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue();
+ }
+
+ @Test
+ public void nameValidationResult_parcel() {
+ ContentValues values = new ContentValues();
+ values.put("name", "Name");
+ values.put("phone_number", "123");
+
+ NameValidationResult result;
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0);
+ parcel.setDataPosition(0);
+ result = parcel.readParcelable(NameValidationResult.class.getClassLoader());
+ } finally {
+ parcel.recycle();
+ }
+
+ assertThat(result.getName()).isEqualTo("name");
+ assertThat(result.getSanitizedName()).isEqualTo("sanitized name");
+ assertThat(result.getEncodedLength()).isEqualTo(1);
+ assertThat(result.getMaxEncodedLength()).isEqualTo(2);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 9705284..cfe3803 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -27,6 +27,10 @@
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
@@ -427,6 +431,27 @@
cutout.getBoundingRectBottom());
}
+ @Test
+ public void testCalculateRelativeRoundedCorners() {
+ mState.setDisplayFrame(new Rect(0, 0, 200, 400));
+ mState.setRoundedCorners(new RoundedCorners(
+ new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10),
+ new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10),
+ new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380),
+ new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)));
+ WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false,
+ false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+ WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+ assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8),
+ windowInsets.getRoundedCorner(POSITION_TOP_LEFT));
+ assertEquals(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8),
+ windowInsets.getRoundedCorner(POSITION_TOP_RIGHT));
+ assertEquals(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 179, 378),
+ windowInsets.getRoundedCorner(POSITION_BOTTOM_RIGHT));
+ assertEquals(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 19, 378),
+ windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT));
+ }
+
private void assertEqualsAndHashCode() {
assertEquals(mState, mState2);
assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
new file mode 100644
index 0000000..8eb13bc
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.graphics.Point;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class RoundedCornerTest {
+
+ @Test
+ public void testGetPosition() {
+ RoundedCorner roundedCorner = new RoundedCorner(
+ RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+ assertThat(roundedCorner.getPosition(), is(RoundedCorner.POSITION_BOTTOM_LEFT));
+ }
+
+ @Test
+ public void testGetRadius() {
+ RoundedCorner roundedCorner = new RoundedCorner(
+ RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+ assertThat(roundedCorner.getRadius(), is(2));
+ }
+
+ @Test
+ public void testGetCenter() {
+ RoundedCorner roundedCorner = new RoundedCorner(
+ RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+ assertThat(roundedCorner.getCenter(), equalTo(new Point(3, 4)));
+ }
+
+ @Test
+ public void testIsEmpty() {
+ RoundedCorner roundedCorner = new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+ assertThat(roundedCorner.isEmpty(), is(true));
+ }
+
+ @Test
+ public void testEquals() {
+ RoundedCorner roundedCorner = new RoundedCorner(
+ RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+ RoundedCorner roundedCorner2 = new RoundedCorner(
+ RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+ assertThat(roundedCorner, equalTo(roundedCorner2));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java
new file mode 100644
index 0000000..07ef33a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class RoundedCornersTest {
+
+ final RoundedCorners mRoundedCorners = new RoundedCorners(
+ new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10),
+ new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10),
+ new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380),
+ new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380));
+
+ @Test
+ public void testGetRoundedCorner() {
+ final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+ RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400);
+
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT),
+ equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT),
+ equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT),
+ equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT),
+ equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)));
+ }
+
+ @Test
+ public void testGetRoundedCorner_noRoundedCorners() {
+ RoundedCorners roundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT), nullValue());
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT), nullValue());
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT), nullValue());
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), nullValue());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertThat(mRoundedCorners.hashCode(),
+ equalTo(RoundedCorners.fromRadii(new Pair<>(10, 20), 200, 400).hashCode()));
+ assertThat(mRoundedCorners.hashCode(),
+ not(equalTo(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400).hashCode())));
+ }
+
+ @Test
+ public void testEquals() {
+ assertThat(mRoundedCorners,
+ equalTo(RoundedCorners.fromRadii(new Pair<>(10, 20), 200, 400)));
+
+ assertThat(mRoundedCorners,
+ not(equalTo(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400))));
+ }
+
+ @Test
+ public void testSetRoundedCorner() {
+ RoundedCorner roundedCorner = new RoundedCorner(POSITION_BOTTOM_LEFT, 5, 6, 7);
+ mRoundedCorners.setRoundedCorner(POSITION_BOTTOM_LEFT, roundedCorner);
+
+ assertThat(mRoundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), equalTo(roundedCorner));
+ }
+
+ @Test
+ public void testSetRoundedCorner_null() {
+ mRoundedCorners.setRoundedCorner(POSITION_BOTTOM_LEFT, null);
+
+ assertThat(mRoundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT],
+ equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT)));
+ assertThat(mRoundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), nullValue());
+ }
+
+ @Test
+ public void testInsetRoundedCorners_partialOverlap() {
+ RoundedCorners roundedCorners = mRoundedCorners.inset(1, 2, 3, 4);
+
+ assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_LEFT],
+ equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8)));
+ assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_RIGHT],
+ equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8)));
+ assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_RIGHT],
+ equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 179, 378)));
+ assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT],
+ equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 19, 378)));
+ }
+
+ @Test
+ public void testInsetRoundedCorners_noOverlap() {
+ RoundedCorners roundedCorners = mRoundedCorners.inset(20, 20, 20, 20);
+
+ assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_LEFT].isEmpty(), is(true));
+ assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_RIGHT].isEmpty(), is(true));
+ assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_RIGHT].isEmpty(), is(true));
+ assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT].isEmpty(), is(true));
+ }
+
+ @Test
+ public void testRotateRoundedCorners_90() {
+ final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+ RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400)
+ .rotate(ROTATION_90, 200, 400);
+
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT),
+ equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT),
+ equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 20, 380, 20)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT),
+ equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 380, 180)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT),
+ equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 10, 10, 190)));
+ }
+
+ @Test
+ public void testRotateRoundedCorners_270() {
+ final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+ RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400)
+ .rotate(ROTATION_270, 200, 400);
+
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT),
+ equalTo(new RoundedCorner(POSITION_TOP_LEFT, 20, 20, 20)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT),
+ equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 390, 10)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT),
+ equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 10, 390, 190)));
+ assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT),
+ equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 180)));
+ }
+
+ @Test
+ public void testFromRadius_cache() {
+ final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+ RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+ assertThat(RoundedCorners.fromRadii(radius, 200, 400), sameInstance(cached));
+ }
+
+ @Test
+ public void testFromRadius_wontCacheIfRadiusChanged() {
+ final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+ RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+ assertThat(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400),
+ not(sameInstance(cached)));
+ }
+
+ @Test
+ public void testFromRadius_wontCacheIfDisplayWidthChanged() {
+ final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+ RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+ assertThat(RoundedCorners.fromRadii(radius, 100, 400),
+ not(sameInstance(cached)));
+ }
+
+ @Test
+ public void testFromRadius_wontCacheIfDisplayHeightChanged() {
+ final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+ RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+ assertThat(RoundedCorners.fromRadii(radius, 200, 300),
+ not(sameInstance(cached)));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 8e4e735..b80837f 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -61,7 +61,7 @@
WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null,
- systemBars(), true /* compatIgnoreVisibility */);
+ null, systemBars(), true /* compatIgnoreVisibility */);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java b/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java
deleted file mode 100644
index 127ecfb..0000000
--- a/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java
+++ /dev/null
@@ -1,194 +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.listeners;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import junit.framework.TestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicReference;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ListenerTransportMultiplexerTest extends TestCase {
-
- TestMultiplexer mMultiplexer;
-
- @Before
- public void setUp() {
- mMultiplexer = new TestMultiplexer();
- }
-
- @Test
- public void testAdd() {
- Runnable runnable = mock(Runnable.class);
-
- mMultiplexer.addListener(0, runnable, Runnable::run);
- assertThat(mMultiplexer.mRegistered).isTrue();
- assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
-
- mMultiplexer.notifyListeners();
- verify(runnable, times(1)).run();
- }
-
- @Test
- public void testAdd_Multiple() {
- Runnable runnable1 = mock(Runnable.class);
- Runnable runnable2 = mock(Runnable.class);
-
- mMultiplexer.addListener(0, runnable1, Runnable::run);
- mMultiplexer.addListener(0, runnable2, Runnable::run);
-
- mMultiplexer.notifyListeners();
- verify(runnable1).run();
- verify(runnable2).run();
- }
-
- @Test
- public void testRemove() {
- Runnable runnable = mock(Runnable.class);
-
- mMultiplexer.addListener(0, runnable, Runnable::run);
- mMultiplexer.removeListener(runnable);
- assertThat(mMultiplexer.mRegistered).isFalse();
-
- mMultiplexer.notifyListeners();
- verify(runnable, never()).run();
- }
-
- @Test
- public void testRemove_Multiple() {
- Runnable runnable1 = mock(Runnable.class);
- Runnable runnable2 = mock(Runnable.class);
-
- mMultiplexer.addListener(0, runnable1, Runnable::run);
- mMultiplexer.addListener(1, runnable2, Runnable::run);
- mMultiplexer.removeListener(runnable1);
-
- mMultiplexer.notifyListeners();
- verify(runnable1, never()).run();
- verify(runnable2).run();
- }
-
- @Test
- public void testMergeMultiple() {
- Runnable runnable1 = mock(Runnable.class);
- Runnable runnable2 = mock(Runnable.class);
- Runnable runnable3 = mock(Runnable.class);
-
- mMultiplexer.addListener(0, runnable1, Runnable::run);
- mMultiplexer.addListener(1, runnable2, Runnable::run);
- assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
-
- mMultiplexer.notifyListeners();
- verify(runnable1, times(1)).run();
- verify(runnable2, times(1)).run();
- verify(runnable3, times(0)).run();
-
- mMultiplexer.addListener(0, runnable3, Runnable::run);
- assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
-
- mMultiplexer.notifyListeners();
- verify(runnable1, times(2)).run();
- verify(runnable2, times(2)).run();
- verify(runnable3, times(1)).run();
-
- mMultiplexer.removeListener(runnable2);
- assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
-
- mMultiplexer.notifyListeners();
- verify(runnable1, times(3)).run();
- verify(runnable2, times(2)).run();
- verify(runnable3, times(2)).run();
-
- mMultiplexer.removeListener(runnable1);
- mMultiplexer.removeListener(runnable3);
- mMultiplexer.notifyListeners();
- verify(runnable1, times(3)).run();
- verify(runnable2, times(2)).run();
- verify(runnable3, times(2)).run();
- }
-
- @Test(timeout = 5000)
- public void testReentrancy() {
- AtomicReference<Runnable> runnable = new AtomicReference<>();
- runnable.set(() -> mMultiplexer.removeListener(runnable.get()));
-
- mMultiplexer.addListener(0, runnable.get(), command -> {
- CountDownLatch latch = new CountDownLatch(1);
- new Handler(Looper.getMainLooper()).post(() -> {
- command.run();
- latch.countDown();
- });
- try {
- latch.await();
- } catch (InterruptedException e) {
- throw new AssertionError(e);
- }
- });
-
- mMultiplexer.notifyListeners();
- assertThat(mMultiplexer.mRegistered).isFalse();
- }
-
- private static class TestMultiplexer extends ListenerTransportMultiplexer<Integer, Runnable> {
-
- boolean mRegistered;
- int mMergedRequest;
-
- TestMultiplexer() {
- }
-
- public void notifyListeners() {
- deliverToListeners(Runnable::run);
- }
-
- @Override
- protected void registerWithServer(Integer mergedRequest) {
- mRegistered = true;
- mMergedRequest = mergedRequest;
- }
-
- @Override
- protected void unregisterWithServer() {
- mRegistered = false;
- }
-
- @Override
- protected Integer mergeRequests(Collection<Integer> requests) {
- return requests.stream().max(Comparator.naturalOrder()).get();
- }
- }
-}
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 59534e4..2c71287 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -114,7 +114,7 @@
}
void apply(PowerCalculator calculator) {
- BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0, false);
+ 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));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 96e9c4a..018a810 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -66,21 +66,17 @@
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(2000);
- final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1, true);
+ final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1);
builder.setConsumedPower(100);
builder.setDischargePercentage(20);
final UidBatteryConsumer.Builder uidBatteryConsumerBuilder =
builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid);
uidBatteryConsumerBuilder.setPackageWithHighestDrain("foo");
- uidBatteryConsumerBuilder.setConsumedPower(200);
uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, 300);
uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
uidBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500);
- uidBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- + BatteryConsumer.POWER_COMPONENT_CPU, 510);
uidBatteryConsumerBuilder.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, 600);
uidBatteryConsumerBuilder.setUsageDurationMillis(
BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, 700);
@@ -90,13 +86,9 @@
final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
builder.getOrCreateSystemBatteryConsumerBuilder(
SystemBatteryConsumer.DRAIN_TYPE_CAMERA);
- systemBatteryConsumerBuilder.setConsumedPower(10000);
systemBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100);
systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
- systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- + BatteryConsumer.POWER_COMPONENT_CPU, 10210);
systemBatteryConsumerBuilder.setUsageDurationMillis(
BatteryConsumer.TIME_COMPONENT_CPU, 10300);
systemBatteryConsumerBuilder.setUsageDurationForCustomComponentMillis(
@@ -114,22 +106,19 @@
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == 2000) {
assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo("foo");
- assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(200);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_USAGE)).isEqualTo(300);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(400);
assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(500);
- assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(510);
assertThat(uidBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.TIME_COMPONENT_CPU)).isEqualTo(600);
assertThat(uidBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND)).isEqualTo(700);
assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(800);
+ assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1710);
} else {
fail("Unexpected UID " + uidBatteryConsumer.getUid());
}
@@ -139,18 +128,15 @@
batteryUsageStats.getSystemBatteryConsumers();
for (SystemBatteryConsumer systemBatteryConsumer : systemBatteryConsumers) {
if (systemBatteryConsumer.getDrainType() == SystemBatteryConsumer.DRAIN_TYPE_CAMERA) {
- assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(10000);
assertThat(systemBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100);
assertThat(systemBatteryConsumer.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200);
- assertThat(systemBatteryConsumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
- + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10210);
assertThat(systemBatteryConsumer.getUsageDurationMillis(
BatteryConsumer.TIME_COMPONENT_CPU)).isEqualTo(10300);
assertThat(systemBatteryConsumer.getUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(10400);
+ assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(30510);
} else {
fail("Unexpected drain type " + systemBatteryConsumer.getDrainType());
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index bf74c8d..1687a78 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -47,9 +47,10 @@
setExternalStatsSyncLocked(new DummyExternalStatsSync());
- final boolean[] supportedBuckets = new boolean[MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS];
- Arrays.fill(supportedBuckets, true);
- mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedBuckets);
+ final boolean[] supportedStandardBuckets
+ = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
+ Arrays.fill(supportedStandardBuckets, true);
+ mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedStandardBuckets, 2);
// A no-op handler.
mHandler = new Handler(Looper.getMainLooper()) {
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 c9c81ac..b9908f4 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -21,10 +21,11 @@
import static com.android.internal.power.MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE;
import static com.android.internal.power.MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON;
import static com.android.internal.power.MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER;
-import static com.android.internal.power.MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS;
+import static com.android.internal.power.MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS;
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 android.os.Parcel;
@@ -47,60 +48,77 @@
@Test
public void testConstruction() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
- final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- if (supportedEnergyBuckets[i]) {
- assertTrue(stats.isEnergyBucketSupported(i));
- assertEquals(0L, stats.getAccumulatedBucketEnergy(i));
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+ if (supportedStandardBuckets[i]) {
+ assertTrue(stats.isStandardBucketSupported(i));
+ assertEquals(0L, stats.getAccumulatedStandardBucketEnergy(i));
} else {
- assertFalse(stats.isEnergyBucketSupported(i));
- assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedBucketEnergy(i));
+ assertFalse(stats.isStandardBucketSupported(i));
+ assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedStandardBucketEnergy(i));
}
}
+ for (int i = 0; i < numCustomBuckets; i++) {
+ assertEquals(0L, stats.getAccumulatedCustomBucketEnergy(i));
+ }
}
@Test
public void testCreateFromTemplate() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
- final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
final MeasuredEnergyStats newStats = MeasuredEnergyStats.createFromTemplate(stats);
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- if (supportedEnergyBuckets[i]) {
- assertTrue(newStats.isEnergyBucketSupported(i));
- assertEquals(0, newStats.getAccumulatedBucketEnergy(i));
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+ if (supportedStandardBuckets[i]) {
+ assertTrue(newStats.isStandardBucketSupported(i));
+ assertEquals(0L, newStats.getAccumulatedStandardBucketEnergy(i));
} else {
- assertFalse(newStats.isEnergyBucketSupported(i));
- assertEquals(ENERGY_DATA_UNAVAILABLE, newStats.getAccumulatedBucketEnergy(i));
+ assertFalse(newStats.isStandardBucketSupported(i));
+ assertEquals(ENERGY_DATA_UNAVAILABLE,
+ newStats.getAccumulatedStandardBucketEnergy(i));
}
}
+ for (int i = 0; i < numCustomBuckets; i++) {
+ assertEquals(0L, newStats.getAccumulatedCustomBucketEnergy(i));
+ }
}
@Test
public void testReadWriteParcel() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
- final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
final Parcel parcel = Parcel.obtain();
stats.writeToParcel(parcel);
@@ -108,94 +126,127 @@
parcel.setDataPosition(0);
MeasuredEnergyStats newStats = new MeasuredEnergyStats(parcel);
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- assertEquals(stats.getAccumulatedBucketEnergy(i),
- newStats.getAccumulatedBucketEnergy(i));
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+ assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+ newStats.getAccumulatedStandardBucketEnergy(i));
}
+ for (int i = 0; i < numCustomBuckets; i++) {
+ assertEquals(stats.getAccumulatedCustomBucketEnergy(i),
+ newStats.getAccumulatedCustomBucketEnergy(i));
+ }
+ assertEquals(ENERGY_DATA_UNAVAILABLE,
+ newStats.getAccumulatedCustomBucketEnergy(numCustomBuckets + 1));
parcel.recycle();
}
@Test
public void testCreateAndReadSummaryFromParcel() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
- final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
final Parcel parcel = Parcel.obtain();
MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
parcel.setDataPosition(0);
MeasuredEnergyStats newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel);
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- assertEquals(stats.isEnergyBucketSupported(i),
- newStats.isEnergyBucketSupported(i));
- assertEquals(stats.getAccumulatedBucketEnergy(i),
- newStats.getAccumulatedBucketEnergy(i));
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+ assertEquals(stats.isStandardBucketSupported(i),
+ newStats.isStandardBucketSupported(i));
+ assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+ newStats.getAccumulatedStandardBucketEnergy(i));
}
+ for (int i = 0; i < numCustomBuckets; i++) {
+ assertEquals(stats.getAccumulatedCustomBucketEnergy(i),
+ newStats.getAccumulatedCustomBucketEnergy(i));
+ }
+ assertEquals(ENERGY_DATA_UNAVAILABLE,
+ newStats.getAccumulatedCustomBucketEnergy(numCustomBuckets + 1));
parcel.recycle();
}
@Test
public void testCreateAndReadSummaryFromParcel_existingTemplate() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
- final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedEnergyBuckets);
- template.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
- template.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
- template.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ final MeasuredEnergyStats template
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ template.updateCustomBucket(0, 50, true);
final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 7, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 63, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63, true);
+ stats.updateCustomBucket(0, 315, true);
+ stats.updateCustomBucket(1, 316, true);
final Parcel parcel = Parcel.obtain();
MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
- final boolean[] newSupportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- newSupportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- newSupportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = true; // switched from false to true
- newSupportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = false; // switched true to false
- final MeasuredEnergyStats newTemplate = new MeasuredEnergyStats(newSupportedEnergyBuckets);
+ final boolean[] newsupportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ newsupportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ newsupportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = true; // switched false > true
+ newsupportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = false; // switched true > false
+ final MeasuredEnergyStats newTemplate
+ = new MeasuredEnergyStats(newsupportedStandardBuckets, numCustomBuckets);
parcel.setDataPosition(0);
final MeasuredEnergyStats newStats =
MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, newTemplate);
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- if (!newSupportedEnergyBuckets[i]) {
- assertFalse(newStats.isEnergyBucketSupported(i));
- assertEquals(ENERGY_DATA_UNAVAILABLE, newStats.getAccumulatedBucketEnergy(i));
- } else if (!supportedEnergyBuckets[i]) {
- assertTrue(newStats.isEnergyBucketSupported(i));
- assertEquals(0L, newStats.getAccumulatedBucketEnergy(i));
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+ if (!newsupportedStandardBuckets[i]) {
+ assertFalse(newStats.isStandardBucketSupported(i));
+ assertEquals(ENERGY_DATA_UNAVAILABLE,
+ newStats.getAccumulatedStandardBucketEnergy(i));
+ } else if (!supportedStandardBuckets[i]) {
+ assertTrue(newStats.isStandardBucketSupported(i));
+ assertEquals(0L, newStats.getAccumulatedStandardBucketEnergy(i));
} else {
- assertTrue(newStats.isEnergyBucketSupported(i));
- assertEquals(stats.getAccumulatedBucketEnergy(i),
- newStats.getAccumulatedBucketEnergy(i));
+ assertTrue(newStats.isStandardBucketSupported(i));
+ assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+ newStats.getAccumulatedStandardBucketEnergy(i));
}
}
+ for (int i = 0; i < numCustomBuckets; i++) {
+ assertEquals(stats.getAccumulatedCustomBucketEnergy(i),
+ newStats.getAccumulatedCustomBucketEnergy(i));
+ }
+ assertEquals(ENERGY_DATA_UNAVAILABLE,
+ newStats.getAccumulatedCustomBucketEnergy(numCustomBuckets + 1));
parcel.recycle();
}
@Test
public void testCreateAndReadSummaryFromParcel_skipZero() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- Arrays.fill(supportedEnergyBuckets, true);
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ Arrays.fill(supportedStandardBuckets, true);
- final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
- // Accumulate energy in one bucket, the rest should be zero
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ // Accumulate energy in one bucket and one custom bucket, the rest should be zero
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
+ stats.updateCustomBucket(1, 60, true);
+ // Let's try parcelling with including zeros
final Parcel includeZerosParcel = Parcel.obtain();
MeasuredEnergyStats.writeSummaryToParcel(stats, includeZerosParcel, false);
includeZerosParcel.setDataPosition(0);
@@ -203,90 +254,180 @@
MeasuredEnergyStats newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(
includeZerosParcel);
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
if (i == ENERGY_BUCKET_SCREEN_ON) {
- assertEquals(stats.isEnergyBucketSupported(i),
- newStats.isEnergyBucketSupported(i));
- assertEquals(stats.getAccumulatedBucketEnergy(i),
- newStats.getAccumulatedBucketEnergy(i));
+ assertEquals(stats.isStandardBucketSupported(i),
+ newStats.isStandardBucketSupported(i));
+ assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+ newStats.getAccumulatedStandardBucketEnergy(i));
} else {
- assertTrue(newStats.isEnergyBucketSupported(i));
- assertEquals(0L, newStats.getAccumulatedBucketEnergy(i));
+ assertTrue(newStats.isStandardBucketSupported(i));
+ assertEquals(0L, newStats.getAccumulatedStandardBucketEnergy(i));
}
}
+ assertEquals(0L, newStats.getAccumulatedCustomBucketEnergy(0));
+ assertEquals(stats.getAccumulatedCustomBucketEnergy(1),
+ newStats.getAccumulatedCustomBucketEnergy(1));
includeZerosParcel.recycle();
+ // Now let's try parcelling with skipping zeros
final Parcel skipZerosParcel = Parcel.obtain();
MeasuredEnergyStats.writeSummaryToParcel(stats, skipZerosParcel, true);
skipZerosParcel.setDataPosition(0);
newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(skipZerosParcel);
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
if (i == ENERGY_BUCKET_SCREEN_ON) {
- assertEquals(stats.isEnergyBucketSupported(i),
- newStats.isEnergyBucketSupported(i));
- assertEquals(stats.getAccumulatedBucketEnergy(i),
- newStats.getAccumulatedBucketEnergy(i));
+ assertEquals(stats.isStandardBucketSupported(i),
+ newStats.isStandardBucketSupported(i));
+ assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+ newStats.getAccumulatedStandardBucketEnergy(i));
} else {
- assertFalse(newStats.isEnergyBucketSupported(i));
- assertEquals(ENERGY_DATA_UNAVAILABLE, newStats.getAccumulatedBucketEnergy(i));
+ assertFalse(newStats.isStandardBucketSupported(i));
+ assertEquals(ENERGY_DATA_UNAVAILABLE,
+ newStats.getAccumulatedStandardBucketEnergy(i));
}
}
+ assertEquals(0L, newStats.getAccumulatedCustomBucketEnergy(0));
+ assertEquals(stats.getAccumulatedCustomBucketEnergy(1),
+ newStats.getAccumulatedCustomBucketEnergy(1));
skipZerosParcel.recycle();
}
@Test
+ public void testCreateAndReadSummaryFromParcel_nullTemplate() {
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
+
+ final Parcel parcel = Parcel.obtain();
+ MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
+ parcel.setDataPosition(0);
+
+ MeasuredEnergyStats newStats
+ = MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, null);
+ assertNull(newStats);
+ parcel.recycle();
+ }
+
+ @Test
+ public void testCreateAndReadSummaryFromParcel_boring() {
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+
+ final MeasuredEnergyStats template
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ template.updateCustomBucket(0, 50, true);
+
+ final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L, true);
+
+ final Parcel parcel = Parcel.obtain();
+ MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
+
+ final boolean[] newSupportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ newSupportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ newSupportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = true; // switched false > true
+ newSupportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = false; // switched true > false
+ final MeasuredEnergyStats newTemplate
+ = new MeasuredEnergyStats(newSupportedStandardBuckets, numCustomBuckets);
+ parcel.setDataPosition(0);
+
+ final MeasuredEnergyStats newStats =
+ MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, newTemplate);
+ // The only non-0 entry in stats is no longer supported, so now there's no interesting data.
+ assertNull(newStats);
+ assertEquals("Parcel was not properly consumed", 0, parcel.dataAvail());
+ parcel.recycle();
+ }
+
+ @Test
public void testUpdateBucket() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
- final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_DOZE, 30, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
- assertEquals(15, stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
+ stats.updateCustomBucket(0, 3, true);
+
+ assertEquals(15, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
assertEquals(ENERGY_DATA_UNAVAILABLE,
- stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_DOZE));
- assertEquals(40, stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_OTHER));
+ stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_DOZE));
+ assertEquals(40, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_OTHER));
+ assertEquals(50 + 3, stats.getAccumulatedCustomBucketEnergy(0));
+ assertEquals(60, stats.getAccumulatedCustomBucketEnergy(1));
}
@Test
public void testReset() {
- final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
- supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+ final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+ final int numCustomBuckets = 2;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+ supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
- final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
- stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
MeasuredEnergyStats.resetIfNotNull(stats);
// All energy should be reset to 0
- for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
- if (supportedEnergyBuckets[i]) {
- assertTrue(stats.isEnergyBucketSupported(i));
- assertEquals(0, stats.getAccumulatedBucketEnergy(i));
+ for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+ if (supportedStandardBuckets[i]) {
+ assertTrue(stats.isStandardBucketSupported(i));
+ assertEquals(0, stats.getAccumulatedStandardBucketEnergy(i));
} else {
- assertFalse(stats.isEnergyBucketSupported(i));
- assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedBucketEnergy(i));
+ assertFalse(stats.isStandardBucketSupported(i));
+ assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedStandardBucketEnergy(i));
}
}
+ for (int i = 0; i < numCustomBuckets; i++) {
+ assertEquals(0, stats.getAccumulatedCustomBucketEnergy(i));
+ }
// Values should increase as usual.
- stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 70, true);
- assertEquals(70L, stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
+ stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70, true);
+ assertEquals(70L, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
+
+ stats.updateCustomBucket(1, 12, true);
+ assertEquals(12L, stats.getAccumulatedCustomBucketEnergy(1));
}
/** Test that states are mapped to the expected energy buckets. Beware of mapping changes. */
@Test
- public void testEnergyBucketMapping() {
+ public void testStandardBucketMapping() {
int exp;
exp = ENERGY_BUCKET_SCREEN_ON;
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTests.java
new file mode 100644
index 0000000..5a3ea35
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTests.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
+import android.util.DisplayMetrics;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link Display}.
+ *
+ * <p>Build/Install/Run:
+ *
+ * atest FrameworksMockingCoreTests:android.view.DisplayTests
+ */
+@RunWith(AndroidJUnit4.class)
+public class DisplayTests {
+
+ private static final int APP_WIDTH = 272;
+ private static final int APP_HEIGHT = 700;
+ // Tablet size device, ROTATION_0 corresponds to portrait.
+ private static final int LOGICAL_WIDTH = 700;
+ private static final int LOGICAL_HEIGHT = 1800;
+
+ // Bounds of the app when the device is in portrait mode.
+ private static Rect sAppBoundsPortrait = buildAppBounds(LOGICAL_WIDTH, LOGICAL_HEIGHT);
+ private static Rect sAppBoundsLandscape = buildAppBounds(LOGICAL_HEIGHT, LOGICAL_WIDTH);
+
+ private StaticMockitoSession mMockitoSession;
+
+ private DisplayManagerGlobal mDisplayManagerGlobal;
+ private Context mApplicationContext;
+ private DisplayInfo mDisplayInfo = new DisplayInfo();
+
+ @Before
+ public void setupTests() {
+ mMockitoSession = mockitoSession()
+ .mockStatic(DisplayManagerGlobal.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // Ensure no adjustments are set before each test.
+ mApplicationContext = ApplicationProvider.getApplicationContext();
+ DisplayAdjustments displayAdjustments =
+ mApplicationContext.getResources().getDisplayAdjustments();
+ displayAdjustments.setFixedRotationAdjustments(null);
+ mApplicationContext.getResources().overrideDisplayAdjustments(null);
+ mApplicationContext.getResources().getConfiguration().windowConfiguration.setAppBounds(
+ null);
+ mApplicationContext.getResources().getConfiguration().windowConfiguration.setMaxBounds(
+ null);
+ mDisplayInfo.rotation = ROTATION_0;
+
+ mDisplayManagerGlobal = mock(DisplayManagerGlobal.class);
+ doReturn(mDisplayInfo).when(mDisplayManagerGlobal).getDisplayInfo(anyInt());
+ }
+
+ @After
+ public void teardownTests() {
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
+ Mockito.framework().clearInlineMocks();
+ }
+
+ @Test
+ public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
+ setDisplayInfoPortrait(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ assertThat(display.getDisplayAdjustments()).isEqualTo(
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ DisplayInfo actualDisplayInfo = new DisplayInfo();
+ display.getDisplayInfo(actualDisplayInfo);
+ verifyDisplayInfo(actualDisplayInfo, mDisplayInfo);
+ }
+
+ @Test
+ public void testConstructor_defaultResources_matchesDisplayInfo() {
+ setDisplayInfoPortrait(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ assertThat(display.getDisplayAdjustments()).isEqualTo(
+ mApplicationContext.getResources().getDisplayAdjustments());
+ DisplayInfo actualDisplayInfo = new DisplayInfo();
+ display.getDisplayInfo(actualDisplayInfo);
+ verifyDisplayInfo(actualDisplayInfo, mDisplayInfo);
+ }
+
+ @Test
+ public void testGetRotation_defaultDisplayAdjustments_rotationNotAdjusted() {
+ setDisplayInfoPortrait(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ assertThat(display.getRotation()).isEqualTo(ROTATION_0);
+ }
+
+ @Test
+ public void testGetRotation_displayAdjustmentsWithoutOverride_rotationNotAdjusted() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated, but no override is set.
+ DisplayAdjustments displayAdjustments = DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+ final FixedRotationAdjustments fixedRotationAdjustments =
+ new FixedRotationAdjustments(ROTATION_90, APP_WIDTH, APP_HEIGHT,
+ DisplayCutout.NO_CUTOUT);
+ displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
+ // GIVEN display is constructed with display adjustments.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ displayAdjustments);
+ // THEN rotation is not adjusted since no override was set.
+ assertThat(display.getRotation()).isEqualTo(ROTATION_0);
+ }
+
+ @Test
+ public void testGetRotation_resourcesWithoutOverride_rotationNotAdjusted() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated, but no override is set.
+ setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN rotation is not adjusted since no override is set.
+ assertThat(display.getRotation()).isEqualTo(ROTATION_0);
+ }
+
+ @Test
+ public void testGetRotation_resourcesWithOverrideDisplayAdjustments_rotationAdjusted() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated, and an override is set.
+ setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN rotation is adjusted since an override is set.
+ assertThat(display.getRotation()).isEqualTo(ROTATION_90);
+ }
+
+ @Test
+ public void testGetRealSize_defaultResourcesPortrait_matchesLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches display orientation.
+ verifyRealSizeIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealSize_defaultResourcesLandscape_matchesRotatedLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches display orientation.
+ verifyRealSizeIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealSize_defaultDisplayAdjustmentsPortrait_matchesLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ // THEN real size matches display orientation.
+ verifyRealSizeIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealSize_defaultDisplayAdjustmentsLandscape_matchesLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ // THEN real size matches display orientation.
+ verifyRealSizeIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealSize_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated.
+ setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches display orientation.
+ verifyRealSizeIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealSize_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated.
+ setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches display orientation.
+ verifyRealSizeIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealSize_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated, and an override is set.
+ setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches app orientation.
+ verifyRealSizeIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealSize_resourcesWithLandscapeOverrideRotation_rotatedLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated, and an override is set.
+ setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches app orientation.
+ verifyRealSizeIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealSize_resourcesPortraitSandboxed_matchesSandboxBounds() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN app is letterboxed.
+ setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(),
+ sAppBoundsPortrait);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches app bounds.
+ verifyRealSizeMatchesApp(display, sAppBoundsPortrait);
+ }
+
+ @Test
+ public void testGetRealSize_resourcesLandscapeSandboxed_matchesSandboxBounds() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ // GIVEN app is letterboxed.
+ setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(),
+ sAppBoundsLandscape);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real size matches app bounds.
+ verifyRealSizeMatchesApp(display, sAppBoundsLandscape);
+ }
+
+ @Test
+ public void testGetRealMetrics_defaultResourcesPortrait_matchesLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches display orientation.
+ verifyRealMetricsIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_defaultResourcesLandscape_matchesRotatedLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches display orientation.
+ verifyRealMetricsIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_defaultDisplayAdjustmentsPortrait_matchesLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ // THEN real metrics matches display orientation.
+ verifyRealMetricsIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_defaultDisplayAdjustmentsLandscape_matchesLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ // THEN real metrics matches display orientation.
+ verifyRealMetricsIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated.
+ setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches display orientation.
+ verifyRealMetricsIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated.
+ setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches display orientation.
+ verifyRealMetricsIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated with an override.
+ setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches app orientation.
+ verifyRealMetricsIsPortrait(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_resourcesWithLandscapeOverrideRotation_rotatedLogicalSize() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN fixed rotation adjustments are rotated.
+ setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ // GIVEN display is constructed with default resources.
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches app orientation.
+ verifyRealMetricsIsLandscape(display);
+ }
+
+ @Test
+ public void testGetRealMetrics_resourcesPortraitSandboxed_matchesSandboxBounds() {
+ // GIVEN display is not rotated.
+ setDisplayInfoPortrait(mDisplayInfo);
+ // GIVEN app is letterboxed.
+ setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(),
+ sAppBoundsPortrait);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches app bounds.
+ verifyRealMetricsMatchesApp(display, sAppBoundsPortrait);
+ }
+
+ @Test
+ public void testGetRealMetrics_resourcesLandscapeSandboxed_matchesSandboxBounds() {
+ // GIVEN display is rotated.
+ setDisplayInfoLandscape(mDisplayInfo);
+ // GIVEN app is letterboxed.
+ setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(),
+ sAppBoundsLandscape);
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ mApplicationContext.getResources());
+ // THEN real metrics matches app bounds.
+ verifyRealMetricsMatchesApp(display, sAppBoundsLandscape);
+ }
+
+ // Given rotated display dimensions, calculate the letterboxed app bounds.
+ private static Rect buildAppBounds(int displayWidth, int displayHeight) {
+ final int midWidth = displayWidth / 2;
+ final int left = midWidth - (APP_WIDTH / 2);
+ final int right = midWidth + (APP_WIDTH / 2);
+ final int midHeight = displayHeight / 2;
+ // Coordinate system starts at top left.
+ final int top = midHeight - (APP_HEIGHT / 2);
+ final int bottom = midHeight + (APP_HEIGHT / 2);
+ return new Rect(left, top, right, bottom);
+ }
+
+ private static void setDisplayInfoLandscape(DisplayInfo displayInfo) {
+ displayInfo.rotation = ROTATION_90;
+ // Flip width & height assignment since the device is rotated.
+ displayInfo.logicalWidth = LOGICAL_HEIGHT;
+ displayInfo.logicalHeight = LOGICAL_WIDTH;
+ }
+
+ private static void setDisplayInfoPortrait(DisplayInfo displayInfo) {
+ displayInfo.rotation = ROTATION_0;
+ displayInfo.logicalWidth = LOGICAL_WIDTH;
+ displayInfo.logicalHeight = LOGICAL_HEIGHT;
+ }
+
+ /**
+ * Set max bounds to be sandboxed to the app bounds, indicating the app is in
+ * size compat mode or letterbox.
+ */
+ private static void setMaxBoundsSandboxedToMatchAppBounds(Resources resources, Rect appBounds) {
+ resources.getConfiguration().windowConfiguration.setMaxBounds(appBounds);
+ }
+
+ /**
+ * Do not compare entire display info, since it is updated to match display the test is run on.
+ */
+ private static void verifyDisplayInfo(DisplayInfo actual, DisplayInfo expected) {
+ assertThat(actual.displayId).isEqualTo(expected.displayId);
+ assertThat(actual.rotation).isEqualTo(expected.rotation);
+ assertThat(actual.logicalWidth).isEqualTo(LOGICAL_WIDTH);
+ assertThat(actual.logicalHeight).isEqualTo(LOGICAL_HEIGHT);
+ }
+
+ private static void verifyRealSizeIsLandscape(Display display) {
+ Point size = new Point();
+ display.getRealSize(size);
+ // Flip the width and height check since the device is rotated.
+ assertThat(size).isEqualTo(new Point(LOGICAL_HEIGHT, LOGICAL_WIDTH));
+ }
+
+ private static void verifyRealMetricsIsLandscape(Display display) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getRealMetrics(metrics);
+ // Flip the width and height check since the device is rotated.
+ assertThat(metrics.widthPixels).isEqualTo(LOGICAL_HEIGHT);
+ assertThat(metrics.heightPixels).isEqualTo(LOGICAL_WIDTH);
+ }
+
+ private static void verifyRealSizeIsPortrait(Display display) {
+ Point size = new Point();
+ display.getRealSize(size);
+ assertThat(size).isEqualTo(new Point(LOGICAL_WIDTH, LOGICAL_HEIGHT));
+ }
+
+ private static void verifyRealMetricsIsPortrait(Display display) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getRealMetrics(metrics);
+ assertThat(metrics.widthPixels).isEqualTo(LOGICAL_WIDTH);
+ assertThat(metrics.heightPixels).isEqualTo(LOGICAL_HEIGHT);
+ }
+
+ private static void verifyRealSizeMatchesApp(Display display, Rect appBounds) {
+ Point size = new Point();
+ display.getRealSize(size);
+ assertThat(size).isEqualTo(new Point(appBounds.width(), appBounds.height()));
+ }
+
+ private static void verifyRealMetricsMatchesApp(Display display, Rect appBounds) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getRealMetrics(metrics);
+ assertThat(metrics.widthPixels).isEqualTo(appBounds.width());
+ assertThat(metrics.heightPixels).isEqualTo(appBounds.height());
+ }
+
+ private static FixedRotationAdjustments setOverrideFixedRotationAdjustments(
+ Resources resources, @Surface.Rotation int rotation) {
+ FixedRotationAdjustments fixedRotationAdjustments =
+ setFixedRotationAdjustments(resources, rotation);
+ resources.overrideDisplayAdjustments(
+ buildOverrideRotationAdjustments(fixedRotationAdjustments));
+ return fixedRotationAdjustments;
+ }
+
+ private static FixedRotationAdjustments setFixedRotationAdjustments(Resources resources,
+ @Surface.Rotation int rotation) {
+ final FixedRotationAdjustments fixedRotationAdjustments =
+ new FixedRotationAdjustments(rotation, APP_WIDTH, APP_HEIGHT,
+ DisplayCutout.NO_CUTOUT);
+ resources.getDisplayAdjustments().setFixedRotationAdjustments(fixedRotationAdjustments);
+ return fixedRotationAdjustments;
+ }
+
+ private static Consumer<DisplayAdjustments> buildOverrideRotationAdjustments(
+ FixedRotationAdjustments fixedRotationAdjustments) {
+ return consumedDisplayAdjustments
+ -> consumedDisplayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
+ }
+}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 301b491..b0ae9b9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -223,14 +223,6 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
</split-permission>
- <split-permission name="android.permission.RECORD_AUDIO"
- targetSdk="31">
- <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" />
- </split-permission>
- <split-permission name="android.permission.CAMERA"
- targetSdk="31">
- <new-permission name="android.permission.BACKGROUND_CAMERA" />
- </split-permission>
<!-- This is a list of all the libraries available for application
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 222c9bd..ac8a296 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1903,12 +1903,6 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "123161180": {
- "message": "SEVER CHILDREN",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
- },
"140319294": {
"message": "IME target changed within ActivityRecord",
"level": "DEBUG",
@@ -2143,6 +2137,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "332390227": {
+ "message": "Sandbox max bounds for uid %s to bounds %s due to letterboxing? %s mismatch with parent bounds? %s size compat mode %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_CONFIGURATION",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"342460966": {
"message": "DRAG %s: pos=(%d,%d)",
"level": "INFO",
@@ -2623,6 +2623,12 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/WindowOrganizerController.java"
},
+ "910200295": {
+ "message": "Sandbox max bounds due to mismatched orientation with parent, to %s vs DisplayArea %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_CONFIGURATION",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"913494177": {
"message": "removeAllWindowsIfPossible: removing win=%s",
"level": "WARN",
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index c1310a9..4a92cf1 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -693,6 +693,32 @@
throw new IllegalArgumentException("Unrecognized outline?");
}
+ /** @hide */
+ public boolean clearStretch() {
+ return nClearStretch(mNativeRenderNode);
+ }
+
+ /** @hide */
+ public boolean stretch(float left, float top, float right, float bottom,
+ float vecX, float vecY, float maxStretchAmount) {
+ if (1.0 < vecX || vecX < -1.0) {
+ throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
+ }
+ if (1.0 < vecY || vecY < -1.0) {
+ throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
+ }
+ if (top <= bottom || right <= left) {
+ throw new IllegalArgumentException(
+ "Stretch region must not be empty, got "
+ + new RectF(left, top, right, bottom).toString());
+ }
+ if (maxStretchAmount <= 0.0f) {
+ throw new IllegalArgumentException(
+ "The max stretch amount must be >0, got " + maxStretchAmount);
+ }
+ return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+ }
+
/**
* Checks if the RenderNode has a shadow. That is, if the combination of {@link #getElevation()}
* and {@link #getTranslationZ()} is greater than zero, there is an {@link Outline} set with
@@ -1638,6 +1664,13 @@
private static native boolean nSetOutlineNone(long renderNode);
@CriticalNative
+ private static native boolean nClearStretch(long renderNode);
+
+ @CriticalNative
+ private static native boolean nStretch(long renderNode, float left, float top, float right,
+ float bottom, float vecX, float vecY, float maxStretch);
+
+ @CriticalNative
private static native boolean nHasShadow(long renderNode);
@CriticalNative
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 7f22dc2..28b3b04 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1465,24 +1465,6 @@
}
/**
- * Notifies the drawable that it has been attached.
- *
- * @param v The view that it is attached to
- * @hide
- */
- public void onAttached(View v) {
- }
-
- /**
- * Notifies the drawable that it has been detached.
- *
- * @param v The view that it is detached from
- * @hide
- */
- public void onDetached(View v) {
- }
-
- /**
* Sets the source override density for this Drawable. If non-zero, this density is to be used
* for any calls to {@link Resources#getDrawableForDensity(int, int, Theme)} or
* {@link Resources#getValueForDensity(int, int, TypedValue, boolean)}.
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index f41b608..ae9f8664 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -19,9 +19,9 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
-import com.android.org.bouncycastle.util.io.pem.PemObject;
-import com.android.org.bouncycastle.util.io.pem.PemReader;
-import com.android.org.bouncycastle.util.io.pem.PemWriter;
+import com.android.internal.org.bouncycastle.util.io.pem.PemObject;
+import com.android.internal.org.bouncycastle.util.io.pem.PemReader;
+import com.android.internal.org.bouncycastle.util.io.pem.PemWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 4a67135..e19d88c 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -45,8 +45,8 @@
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import java.io.ByteArrayInputStream;
import java.io.IOException;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 6ad8d2c..334b111 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -26,24 +26,24 @@
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
-import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.ASN1Integer;
-import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import com.android.org.bouncycastle.asn1.DERBitString;
-import com.android.org.bouncycastle.asn1.DERNull;
-import com.android.org.bouncycastle.asn1.DERSequence;
-import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import com.android.org.bouncycastle.asn1.x509.Certificate;
-import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import com.android.org.bouncycastle.asn1.x509.TBSCertificate;
-import com.android.org.bouncycastle.asn1.x509.Time;
-import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import com.android.org.bouncycastle.jce.X509Principal;
-import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
-import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.ASN1Integer;
+import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.internal.org.bouncycastle.asn1.DERBitString;
+import com.android.internal.org.bouncycastle.asn1.DERNull;
+import com.android.internal.org.bouncycastle.asn1.DERSequence;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.internal.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import com.android.internal.org.bouncycastle.asn1.x509.Certificate;
+import com.android.internal.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.x509.TBSCertificate;
+import com.android.internal.org.bouncycastle.asn1.x509.Time;
+import com.android.internal.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import com.android.internal.org.bouncycastle.jce.X509Principal;
+import com.android.internal.org.bouncycastle.jce.provider.X509CertificateObject;
+import com.android.internal.org.bouncycastle.x509.X509V3CertificateGenerator;
import libcore.util.EmptyArray;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index b1b6306..16bf546 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -23,7 +23,6 @@
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterDefs;
-import android.sysprop.Keystore2Properties;
import java.io.IOException;
import java.security.KeyFactory;
@@ -117,8 +116,6 @@
putSecretKeyFactoryImpl("HmacSHA512");
}
- private static boolean sKeystore2Enabled;
-
/**
* This function indicates whether or not Keystore 2.0 is enabled. Some parts of the
* Keystore SPI must behave subtly differently when Keystore 2.0 is enabled. However,
@@ -133,10 +130,9 @@
* @hide
*/
public static boolean isKeystore2Enabled() {
- return sKeystore2Enabled;
+ return android.security.keystore2.AndroidKeyStoreProvider.isInstalled();
}
-
/**
* Installs a new instance of this provider (and the
* {@link AndroidKeyStoreBCWorkaroundProvider}).
@@ -164,11 +160,6 @@
// priority.
Security.addProvider(workaroundProvider);
}
-
- // {@code install()} is run by zygote when this property is still accessible. We store its
- // value so that the Keystore SPI can act accordingly without having to access an internal
- // property.
- sKeystore2Enabled = Keystore2Properties.keystore2_enabled().orElse(false);
}
private void putSecretKeyFactoryImpl(String algorithm) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 3694d63..d2678c7 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -215,7 +215,8 @@
// Keystore 1.0 does not tell us the exact security level of the key
// so we report an unknown but secure security level.
insideSecureHardware ? KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE
- : KeyProperties.SECURITY_LEVEL_SOFTWARE);
+ : KeyProperties.SECURITY_LEVEL_SOFTWARE,
+ KeyProperties.UNRESTRICTED_USAGE_COUNT);
}
private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index e9aac7d..c2a7b2e 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -274,6 +274,7 @@
private final boolean mUserConfirmationRequired;
private final boolean mUnlockedDeviceRequired;
private final boolean mCriticalToDeviceEncryption;
+ private final int mMaxUsageCount;
/*
* ***NOTE***: All new fields MUST also be added to the following:
* ParcelableKeyGenParameterSpec class.
@@ -313,7 +314,8 @@
boolean isStrongBoxBacked,
boolean userConfirmationRequired,
boolean unlockedDeviceRequired,
- boolean criticalToDeviceEncryption) {
+ boolean criticalToDeviceEncryption,
+ int maxUsageCount) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -366,6 +368,7 @@
mUserConfirmationRequired = userConfirmationRequired;
mUnlockedDeviceRequired = unlockedDeviceRequired;
mCriticalToDeviceEncryption = criticalToDeviceEncryption;
+ mMaxUsageCount = maxUsageCount;
}
/**
@@ -782,7 +785,7 @@
}
/**
- * Return whether this key is critical to the device encryption flow.
+ * Returns whether this key is critical to the device encryption flow.
*
* @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
* @hide
@@ -792,6 +795,17 @@
}
/**
+ * Returns the maximum number of times the limited use key is allowed to be used or
+ * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there’s no restriction on the number of
+ * times the key can be used.
+ *
+ * @see Builder#setMaxUsageCount(int)
+ */
+ public int getMaxUsageCount() {
+ return mMaxUsageCount;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -827,6 +841,7 @@
private boolean mUserConfirmationRequired;
private boolean mUnlockedDeviceRequired = false;
private boolean mCriticalToDeviceEncryption = false;
+ private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
/**
* Creates a new instance of the {@code Builder}.
@@ -894,6 +909,7 @@
mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
+ mMaxUsageCount = sourceSpec.getMaxUsageCount();
}
/**
@@ -1553,6 +1569,47 @@
}
/**
+ * Sets the maximum number of times the key is allowed to be used. After every use of the
+ * key, the use counter will decrease. This authorization applies only to secret key and
+ * private key operations. Public key operations are not restricted. For example, after
+ * successfully encrypting and decrypting data using methods such as
+ * {@link Cipher#doFinal()}, the use counter of the secret key will decrease. After
+ * successfully signing data using methods such as {@link Signature#sign()}, the use
+ * counter of the private key will decrease.
+ *
+ * When the use counter is depleted, the key will be marked for deletion by Android
+ * Keystore and any subsequent attempt to use the key will throw
+ * {@link KeyPermanentlyInvalidatedException}. There is no key to be loaded from the
+ * Android Keystore once the exhausted key is permanently deleted, as if the key never
+ * existed before.
+ *
+ * <p>By default, there is no restriction on the usage of key.
+ *
+ * <p>Some secure hardware may not support this feature at all, in which case it will
+ * be enforced in software, some secure hardware may support it but only with
+ * maxUsageCount = 1, and some secure hardware may support it with larger value
+ * of maxUsageCount.
+ *
+ * <p>The PackageManger feature flags:
+ * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY} and
+ * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY} can be used
+ * to check whether the secure hardware cannot enforce this feature, can only enforce it
+ * with maxUsageCount = 1, or can enforce it with larger value of maxUsageCount.
+ *
+ * @param maxUsageCount maximum number of times the key is allowed to be used or
+ * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there is no restriction on the
+ * usage.
+ */
+ @NonNull
+ public Builder setMaxUsageCount(int maxUsageCount) {
+ if (maxUsageCount == KeyProperties.UNRESTRICTED_USAGE_COUNT || maxUsageCount > 0) {
+ mMaxUsageCount = maxUsageCount;
+ return this;
+ }
+ throw new IllegalArgumentException("maxUsageCount is not valid");
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1587,7 +1644,8 @@
mIsStrongBoxBacked,
mUserConfirmationRequired,
mUnlockedDeviceRequired,
- mCriticalToDeviceEncryption);
+ mCriticalToDeviceEncryption,
+ mMaxUsageCount);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index 7158d0c..f50efd2 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -85,6 +85,7 @@
private final boolean mInvalidatedByBiometricEnrollment;
private final boolean mUserConfirmationRequired;
private final @KeyProperties.SecurityLevelEnum int mSecurityLevel;
+ private final int mRemainingUsageCount;
/**
* @hide
@@ -109,7 +110,8 @@
boolean trustedUserPresenceRequired,
boolean invalidatedByBiometricEnrollment,
boolean userConfirmationRequired,
- @KeyProperties.SecurityLevelEnum int securityLevel) {
+ @KeyProperties.SecurityLevelEnum int securityLevel,
+ int remainingUsageCount) {
mKeystoreAlias = keystoreKeyAlias;
mInsideSecureHardware = insideSecureHardware;
mOrigin = origin;
@@ -134,6 +136,7 @@
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
mUserConfirmationRequired = userConfirmationRequired;
mSecurityLevel = securityLevel;
+ mRemainingUsageCount = remainingUsageCount;
}
/**
@@ -374,4 +377,15 @@
public @KeyProperties.SecurityLevelEnum int getSecurityLevel() {
return mSecurityLevel;
}
+
+ /**
+ * Returns the remaining number of times the key is allowed to be used or
+ * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there's no restriction on the number of
+ * times the key can be used. Note that this gives a best effort count and need not be
+ * accurate (as there might be usages happening in parallel and the count maintained here need
+ * not be in sync with the usage).
+ */
+ public int getRemainingUsageCount() {
+ return mRemainingUsageCount;
+ }
}
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 014d688..fa4f8b1 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -907,4 +907,9 @@
+ uid);
}
}
+
+ /**
+ * This value indicates that there is no restriction on the number of times the key can be used.
+ */
+ public static final int UNRESTRICTED_USAGE_COUNT = -1;
}
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index e1e404d..aaa3715 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -235,6 +235,7 @@
private final boolean mUserConfirmationRequired;
private final boolean mUnlockedDeviceRequired;
private final boolean mIsStrongBoxBacked;
+ private final int mMaxUsageCount;
private KeyProtection(
Date keyValidityStart,
@@ -256,7 +257,8 @@
boolean criticalToDeviceEncryption,
boolean userConfirmationRequired,
boolean unlockedDeviceRequired,
- boolean isStrongBoxBacked) {
+ boolean isStrongBoxBacked,
+ int maxUsageCount) {
mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -279,6 +281,7 @@
mUserConfirmationRequired = userConfirmationRequired;
mUnlockedDeviceRequired = unlockedDeviceRequired;
mIsStrongBoxBacked = isStrongBoxBacked;
+ mMaxUsageCount = maxUsageCount;
}
/**
@@ -548,6 +551,17 @@
}
/**
+ * Returns the maximum number of times the limited use key is allowed to be used or
+ * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there’s no restriction on the number of
+ * times the key can be used.
+ *
+ * @see Builder#setMaxUsageCount(int)
+ */
+ public int getMaxUsageCount() {
+ return mMaxUsageCount;
+ }
+
+ /**
* Builder of {@link KeyProtection} instances.
*/
public final static class Builder {
@@ -574,6 +588,7 @@
private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
private boolean mCriticalToDeviceEncryption = false;
private boolean mIsStrongBoxBacked = false;
+ private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
/**
* Creates a new instance of the {@code Builder}.
@@ -1039,6 +1054,47 @@
}
/**
+ * Sets the maximum number of times the key is allowed to be used. After every use of the
+ * key, the use counter will decrease. This authorization applies only to secret key and
+ * private key operations. Public key operations are not restricted. For example, after
+ * successfully encrypting and decrypting data using methods such as
+ * {@link Cipher#doFinal()}, the use counter of the secret key will decrease. After
+ * successfully signing data using methods such as {@link Signature#sign()}, the use
+ * counter of the private key will decrease.
+ *
+ * When the use counter is depleted, the key will be marked for deletion by Android
+ * Keystore and any subsequent attempt to use the key will throw
+ * {@link KeyPermanentlyInvalidatedException}. There is no key to be loaded from the
+ * Android Keystore once the exhausted key is permanently deleted, as if the key never
+ * existed before.
+ *
+ * <p>By default, there is no restriction on the usage of key.
+ *
+ * <p>Some secure hardware may not support this feature at all, in which case it will
+ * be enforced in software, some secure hardware may support it but only with
+ * maxUsageCount = 1, and some secure hardware may support it with larger value
+ * of maxUsageCount.
+ *
+ * <p>The PackageManger feature flags:
+ * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY} and
+ * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY} can be used
+ * to check whether the secure hardware cannot enforce this feature, can only enforce it
+ * with maxUsageCount = 1, or can enforce it with larger value of maxUsageCount.
+ *
+ * @param maxUsageCount maximum number of times the key is allowed to be used or
+ * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there is no restriction on the
+ * usage.
+ */
+ @NonNull
+ public Builder setMaxUsageCount(int maxUsageCount) {
+ if (maxUsageCount == KeyProperties.UNRESTRICTED_USAGE_COUNT || maxUsageCount > 0) {
+ mMaxUsageCount = maxUsageCount;
+ return this;
+ }
+ throw new IllegalArgumentException("maxUsageCount is not valid");
+ }
+
+ /**
* Builds an instance of {@link KeyProtection}.
*
* @throws IllegalArgumentException if a required field is missing
@@ -1065,7 +1121,8 @@
mCriticalToDeviceEncryption,
mUserConfirmationRequired,
mUnlockedDeviceRequired,
- mIsStrongBoxBacked);
+ mIsStrongBoxBacked,
+ mMaxUsageCount);
}
}
}
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 69c15cc..8163472 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -108,6 +108,7 @@
out.writeBoolean(mSpec.isUserConfirmationRequired());
out.writeBoolean(mSpec.isUnlockedDeviceRequired());
out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
+ out.writeInt(mSpec.getMaxUsageCount());
}
private static Date readDateOrNull(Parcel in) {
@@ -166,6 +167,7 @@
final boolean userConfirmationRequired = in.readBoolean();
final boolean unlockedDeviceRequired = in.readBoolean();
final boolean criticalToDeviceEncryption = in.readBoolean();
+ final int maxUsageCount = in.readInt();
// The KeyGenParameterSpec is intentionally not constructed using a Builder here:
// The intention is for this class to break if new parameters are added to the
// KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -199,7 +201,8 @@
isStrongBoxBacked,
userConfirmationRequired,
unlockedDeviceRequired,
- criticalToDeviceEncryption);
+ criticalToDeviceEncryption,
+ maxUsageCount);
}
public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
index 233f352..1575bb4 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
@@ -366,6 +366,13 @@
));
}
+ if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+ params.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+ spec.getMaxUsageCount()
+ ));
+ }
+
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index df0e146..6a92980 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -586,6 +586,13 @@
));
}
+ if (mSpec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+ params.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+ mSpec.getMaxUsageCount()
+ ));
+ }
+
addAlgorithmSpecificParameters(params);
if (mSpec.isUniqueIdIncluded()) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
index 74503e1..fe05989 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -95,6 +95,7 @@
boolean userAuthenticationValidWhileOnBody = false;
boolean trustedUserPresenceRequired = false;
boolean trustedUserConfirmationRequired = false;
+ int remainingUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
try {
for (Authorization a : key.getAuthorizations()) {
switch (a.keyParameter.tag) {
@@ -195,6 +196,16 @@
trustedUserConfirmationRequired =
KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
break;
+ case KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT:
+ long remainingUsageCountUnsigned =
+ KeyStore2ParameterUtils.getUnsignedInt(a);
+ if (remainingUsageCountUnsigned > Integer.MAX_VALUE) {
+ throw new ProviderException(
+ "Usage count of limited use key too long: "
+ + remainingUsageCountUnsigned);
+ }
+ remainingUsageCount = (int) remainingUsageCountUnsigned;
+ break;
}
}
} catch (IllegalArgumentException e) {
@@ -247,7 +258,8 @@
trustedUserPresenceRequired,
invalidatedByBiometricEnrollment,
trustedUserConfirmationRequired,
- securityLevel);
+ securityLevel,
+ remainingUsageCount);
}
private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 07169ce..8c8acc4 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -535,6 +535,12 @@
spec.getKeyValidityForConsumptionEnd()
));
}
+ if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+ importArgs.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+ spec.getMaxUsageCount()
+ ));
+ }
} catch (IllegalArgumentException | IllegalStateException e) {
throw new KeyStoreException(e);
}
@@ -772,6 +778,12 @@
params.getKeyValidityForConsumptionEnd()
));
}
+ if (params.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+ importArgs.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+ params.getMaxUsageCount()
+ ));
+ }
} catch (IllegalArgumentException | IllegalStateException e) {
throw new KeyStoreException(e);
}
diff --git a/packages/SystemUI/res/drawable/btn_restart.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/btn_restart.xml
rename to libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
index b581f55..93a6e7b 100644
--- a/libs/WindowManager/Shell/res/layout/pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml
@@ -97,4 +97,14 @@
android:padding="@dimen/pip_resize_handle_padding"
android:src="@drawable/pip_resize_handle"
android:background="?android:selectableItemBackgroundBorderless" />
-</FrameLayout>
+
+ <!-- invisible layer to trap the focus, b/169372603 -->
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ android:defaultFocusHighlightEnabled="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:focusedByDefault="true" />
+t </FrameLayout>
diff --git a/packages/SystemUI/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
similarity index 100%
rename from packages/SystemUI/res/layout/size_compat_mode_hint.xml
rename to libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 30ef72c..b1425e4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -146,4 +146,10 @@
<!-- Content description to tell the user a bubble has been dismissed. -->
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
+
+ <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
+ <string name="restart_button_description">Tap to restart this app and go full screen.</string>
+
+ <!-- Generic "got it" acceptance of dialog or cling [CHAR LIMIT=NONE] -->
+ <string name="got_it">Got it</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 4b3fc81..afe523a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -106,5 +106,4 @@
public String toString() {
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
}
-
}
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 52648d9..fe97e24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -16,7 +16,7 @@
package com.android.wm.shell;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+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.common.ShellExecutor;
@@ -155,7 +155,7 @@
}
final int taskId = new Integer(args[2]);
final int sideStagePosition = args.length > 3
- ? new Integer(args[3]) : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+ ? new Integer(args[3]) : STAGE_POSITION_BOTTOM_OR_RIGHT;
mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
return true;
}
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 c9b38d0..a570c0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -44,6 +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.startingsurface.StartingSurfaceDrawer;
import java.io.PrintWriter;
@@ -102,18 +103,31 @@
private final Object mLock = new Object();
private final StartingSurfaceDrawer mStartingSurfaceDrawer;
+ /**
+ * In charge of showing size compat UI. Can be {@code null} if device doesn't support size
+ * compat.
+ */
+ @Nullable
+ private final SizeCompatUI mSizeCompatUI;
+
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
- this(null, mainExecutor, context);
+ this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
+ }
+
+ public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
+ SizeCompatUI sizeCompatUI) {
+ this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
- Context context) {
+ Context context, @Nullable SizeCompatUI 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
// ActivityRecord#addStartingWindow to WMShell.
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
+ mSizeCompatUI = sizeCompatUI;
}
@Override
@@ -255,6 +269,7 @@
if (listener != null) {
listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
+ notifySizeCompatUI(info.getTaskInfo(), listener);
}
@Override
@@ -270,6 +285,10 @@
if (!updated && newListener != null) {
newListener.onTaskInfoChanged(taskInfo);
}
+ if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
+ // Notify the size compat UI if the listener or task info changed.
+ notifySizeCompatUI(taskInfo, newListener);
+ }
}
}
@@ -294,6 +313,8 @@
if (listener != null) {
listener.onTaskVanished(taskInfo);
}
+ // Pass null for listener to remove the size compat UI on this task if there is any.
+ notifySizeCompatUI(taskInfo, null /* taskListener */);
}
}
@@ -320,6 +341,34 @@
return true;
}
+ /**
+ * Notifies {@link SizeCompatUI} 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
+ * vanished.
+ */
+ private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
+ if (mSizeCompatUI == null) {
+ return;
+ }
+
+ // The task is vanished, notify to remove size compat UI on this Task if there is any.
+ if (taskListener == null) {
+ mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+ null /* taskConfig */, null /* sizeCompatActivity*/,
+ null /* taskListener */);
+ return;
+ }
+
+ mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+ taskInfo.configuration.windowConfiguration.getBounds(),
+ // null if the top activity not in size compat.
+ taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null,
+ taskListener);
+ }
+
private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/);
}
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 7538c8b..2b53257 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
@@ -771,7 +771,8 @@
// if the bubble is already active, there's no need to push it to overflow
return;
}
- bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble),
+ bubble.inflate(
+ (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
mContext, this, mStackView, mBubbleIconFactory, true /* skipInflation */);
});
return null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 9d196ba..53b7537 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -542,7 +542,8 @@
void overflowBubble(@DismissReason int reason, Bubble bubble) {
if (bubble.getPendingIntentCanceled()
|| !(reason == Bubbles.DISMISS_AGED
- || reason == Bubbles.DISMISS_USER_GESTURE)) {
+ || reason == Bubbles.DISMISS_USER_GESTURE
+ || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
if (DEBUG_BUBBLE_DATA) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
index 3361c4c..c88a58b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
@@ -61,7 +61,10 @@
BUBBLE_OVERFLOW_REMOVE_BLOCKED(490),
@UiEvent(doc = "User selected the overflow.")
- BUBBLE_OVERFLOW_SELECTED(600);
+ BUBBLE_OVERFLOW_SELECTED(600),
+
+ @UiEvent(doc = "Restore bubble to overflow after phone reboot.")
+ BUBBLE_OVERFLOW_RECOVER(691);
private final int mId;
@@ -112,6 +115,8 @@
log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
} else if (r == Bubbles.DISMISS_USER_GESTURE) {
log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
+ } else if (r == Bubbles.DISMISS_RELOAD_FROM_DISK) {
+ log(b, Event.BUBBLE_OVERFLOW_RECOVER);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 6102147..6a1026b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -54,7 +54,7 @@
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
- DISMISS_NO_BUBBLE_UP})
+ DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK})
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
@interface DismissReason {}
@@ -72,6 +72,7 @@
int DISMISS_SHORTCUT_REMOVED = 12;
int DISMISS_PACKAGE_REMOVED = 13;
int DISMISS_NO_BUBBLE_UP = 14;
+ int DISMISS_RELOAD_FROM_DISK = 15;
/**
* @return {@code true} if there is a bubble associated with the provided key and if its
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index e1f831e..73371e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -62,8 +62,8 @@
private static final String TAG = "Bubbs.StackCtrl";
- /** Values to use for animating bubbles in. */
- private static final float ANIMATE_IN_STIFFNESS = 1000f;
+ /** Value to use for animating bubbles in and springing stack after fling. */
+ private static final float STACK_SPRING_STIFFNESS = 700f;
/** Values to use for animating updated bubble to top of stack. */
private static final float NEW_BUBBLE_START_SCALE = 0.5f;
@@ -80,20 +80,15 @@
private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
new PhysicsAnimator.SpringConfig(
- ANIMATE_IN_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+ STACK_SPRING_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
/**
* Friction applied to fling animations. Since the stack must land on one of the sides of the
* screen, we want less friction horizontally so that the stack has a better chance of making it
* to the side without needing a spring.
*/
- private static final float FLING_FRICTION = 2.2f;
+ private static final float FLING_FRICTION = 1.9f;
- /**
- * Values to use for the stack spring animation used to spring the stack to its final position
- * after a fling.
- */
- private static final int SPRING_AFTER_FLING_STIFFNESS = 750;
private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
/** Sentinel value for unset position value. */
@@ -216,7 +211,7 @@
@Override
public void moveToBounds(@NonNull Rect bounds) {
- springStack(bounds.left, bounds.top, SpringForce.STIFFNESS_LOW);
+ springStack(bounds.left, bounds.top, STACK_SPRING_STIFFNESS);
}
@NonNull
@@ -341,7 +336,7 @@
* flings.
*/
public void springStackAfterFling(float destinationX, float destinationY) {
- springStack(destinationX, destinationY, SPRING_AFTER_FLING_STIFFNESS);
+ springStack(destinationX, destinationY, STACK_SPRING_STIFFNESS);
}
/**
@@ -371,7 +366,7 @@
final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
final float stiffness = Settings.Secure.getFloat(contentResolver, "bubble_stiffness",
- SPRING_AFTER_FLING_STIFFNESS /* default */);
+ STACK_SPRING_STIFFNESS /* default */);
final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
SPRING_AFTER_FLING_DAMPING_RATIO);
final float friction = Settings.Secure.getFloat(contentResolver, "bubble_friction",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index a2cd683..b2ac61c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -159,6 +159,14 @@
}
}
+ private void dispatchVisibilityChanged(int displayId, boolean isShowing) {
+ synchronized (mPositionProcessors) {
+ for (ImePositionProcessor pp : mPositionProcessors) {
+ pp.onImeVisibilityChanged(displayId, isShowing);
+ }
+ }
+ }
+
/**
* Adds an {@link ImePositionProcessor} to be called during ime position updates.
*/
@@ -212,7 +220,7 @@
return;
}
- mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
+ updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME));
final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
final Rect newFrame = newSource.getFrame();
@@ -371,7 +379,7 @@
seek = true;
}
mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
- mImeShowing = show;
+ updateImeVisibility(show);
mAnimation = ValueAnimator.ofFloat(startY, endY);
mAnimation.setDuration(
show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
@@ -455,6 +463,13 @@
}
}
+ private void updateImeVisibility(boolean isShowing) {
+ if (mImeShowing != isShowing) {
+ mImeShowing = isShowing;
+ dispatchVisibilityChanged(mDisplayId, isShowing);
+ }
+ }
+
@VisibleForTesting
@BinderThread
public class DisplayWindowInsetsControllerImpl
@@ -563,6 +578,15 @@
default void onImeEndPositioning(int displayId, boolean cancel,
SurfaceControl.Transaction t) {
}
+
+ /**
+ * Called when the IME visibility changed.
+ *
+ * @param isShowing {@code true} if the IME is shown.
+ */
+ default void onImeVisibilityChanged(int displayId, boolean isShowing) {
+
+ }
}
public IInputMethodManager getImms() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 00bd9e5..59374a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -19,7 +19,6 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ITaskStackListener;
import android.content.ComponentName;
-import android.os.IBinder;
import android.window.TaskSnapshot;
import androidx.annotation.BinderThread;
@@ -85,6 +84,4 @@
default void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
default void onActivityRotation(int displayId) { }
-
- default void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index db34248..e94080a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -22,7 +22,6 @@
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Message;
import android.os.Trace;
import android.util.Log;
@@ -54,13 +53,12 @@
private static final int ON_TASK_MOVED_TO_FRONT = 12;
private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 13;
private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 14;
- private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 15;
- private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 16;
- private static final int ON_TASK_DISPLAY_CHANGED = 17;
- private static final int ON_TASK_LIST_UPDATED = 18;
- private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 19;
- private static final int ON_TASK_DESCRIPTION_CHANGED = 20;
- private static final int ON_ACTIVITY_ROTATION = 21;
+ private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 15;
+ private static final int ON_TASK_DISPLAY_CHANGED = 16;
+ private static final int ON_TASK_LIST_UPDATED = 17;
+ private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 18;
+ private static final int ON_TASK_DESCRIPTION_CHANGED = 19;
+ private static final int ON_ACTIVITY_ROTATION = 20;
/**
* List of {@link TaskStackListenerCallback} registered from {@link #addListener}.
@@ -264,13 +262,6 @@
}
@Override
- public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
- mMainHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
- 0 /* unused */,
- activityToken).sendToTarget();
- }
-
- @Override
public boolean handleMessage(Message msg) {
synchronized (mTaskStackListeners) {
switch (msg.what) {
@@ -383,13 +374,6 @@
}
break;
}
- case ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onSizeCompatModeActivityChanged(
- msg.arg1, (IBinder) msg.obj);
- }
- break;
- }
case ON_BACK_PRESSED_ON_TASK_ROOT: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onBackPressedOnTaskRoot(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 800150c..35dcdd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -34,8 +34,11 @@
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -84,8 +87,7 @@
private DragSession mSession;
public DragAndDropPolicy(Context context, SplitScreen splitScreen) {
- this(context, ActivityTaskManager.getInstance(), splitScreen,
- new DefaultStarter(context, splitScreen));
+ this(context, ActivityTaskManager.getInstance(), splitScreen, new DefaultStarter(context));
}
@VisibleForTesting
@@ -94,7 +96,7 @@
mContext = context;
mActivityTaskManager = activityTaskManager;
mSplitScreen = splitScreen;
- mStarter = starter;
+ mStarter = mSplitScreen != null ? mSplitScreen : starter;
}
/**
@@ -195,39 +197,23 @@
return;
}
- final ClipDescription description = data.getDescription();
- final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
- final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
- final Intent dragData = mSession.dragData;
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
final boolean leftOrTop = target.type == TYPE_SPLIT_TOP || target.type == TYPE_SPLIT_LEFT;
- final Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
- ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
- : new Bundle();
- if (target.type == TYPE_FULLSCREEN) {
- // Exit split stages if needed
- mStarter.exitSplitScreen();
- } else if (mSplitScreen != null) {
+ @SplitScreen.StageType int stage = STAGE_TYPE_UNDEFINED;
+ @SplitScreen.StagePosition int position = STAGE_POSITION_UNDEFINED;
+ if (target.type != TYPE_FULLSCREEN && mSplitScreen != null) {
// Update launch options for the split side we are targeting.
- final int position = leftOrTop
- ? SIDE_STAGE_POSITION_TOP_OR_LEFT : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+ position = leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
if (!inSplitScreen) {
- // Update the side stage position to match where we want to launch.
- mSplitScreen.setSideStagePosition(position);
+ // Launch in the side stage if we are not in split-screen already.
+ stage = STAGE_TYPE_SIDE;
}
- mSplitScreen.updateActivityOptions(opts, position);
}
- if (isTask) {
- mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
- } else if (isShortcut) {
- mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME),
- dragData.getStringExtra(EXTRA_SHORTCUT_ID),
- opts, dragData.getParcelableExtra(EXTRA_USER));
- } else {
- mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
- }
+ final ClipDescription description = data.getDescription();
+ final Intent dragData = mSession.dragData;
+ mStarter.startClipDescription(description, dragData, stage, position);
}
/**
@@ -247,7 +233,6 @@
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean runningTaskIsResizeable;
boolean dragItemSupportsSplitscreen;
- boolean isPhone;
DragSession(Context context, ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
@@ -275,7 +260,6 @@
final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
dragItemSupportsSplitscreen = info == null
|| ActivityInfo.isResizeableMode(info.resizeMode);
- isPhone = mContext.getResources().getConfiguration().smallestScreenWidthDp < 600;
dragData = mInitialDragData.getItemAt(0).getIntent();
}
}
@@ -284,11 +268,33 @@
* Interface for actually committing the task launches.
*/
@VisibleForTesting
- interface Starter {
- void startTask(int taskId, Bundle activityOptions);
- void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
- UserHandle user);
- void startIntent(PendingIntent intent, Bundle activityOptions);
+ public interface Starter {
+ default void startClipDescription(ClipDescription description, Intent intent,
+ @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position) {
+ final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+ final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
+ final Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
+ ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) : new Bundle();
+
+ if (isTask) {
+ final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+ startTask(taskId, stage, position, opts);
+ } else if (isShortcut) {
+ final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+ final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
+ final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
+ startShortcut(packageName, id, stage, position, opts, user);
+ } else {
+ startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), stage, position, opts);
+ }
+ }
+ void startTask(int taskId, @SplitScreen.StageType int stage,
+ @SplitScreen.StagePosition int position, @Nullable Bundle options);
+ void startShortcut(String packageName, String shortcutId,
+ @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
+ @Nullable Bundle options, UserHandle user);
+ void startIntent(PendingIntent intent, @SplitScreen.StageType int stage,
+ @SplitScreen.StagePosition int position, @Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
void exitSplitScreen();
}
@@ -299,39 +305,39 @@
*/
private static class DefaultStarter implements Starter {
private final Context mContext;
- private final SplitScreen mSplitScreen;
- public DefaultStarter(Context context, SplitScreen splitScreen) {
+ public DefaultStarter(Context context) {
mContext = context;
- mSplitScreen = splitScreen;
}
@Override
- public void startTask(int taskId, Bundle activityOptions) {
+ public void startTask(int taskId, int stage, int position,
+ @Nullable Bundle options) {
try {
- ActivityTaskManager.getService().startActivityFromRecents(taskId, activityOptions);
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
}
@Override
- public void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
- UserHandle user) {
+ public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ @Nullable Bundle options, UserHandle user) {
try {
LauncherApps launcherApps =
mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- activityOptions, user);
+ options, user);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
}
@Override
- public void startIntent(PendingIntent intent, Bundle activityOptions) {
+ public void startIntent(PendingIntent intent, int stage, int position,
+ @Nullable Bundle options) {
try {
- intent.send(null, 0, null, null, null, null, activityOptions);
+ intent.send(null, 0, null, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
}
@@ -339,14 +345,12 @@
@Override
public void enterSplitScreen(int taskId, boolean leftOrTop) {
- mSplitScreen.moveToSideStage(taskId,
- leftOrTop ? SIDE_STAGE_POSITION_TOP_OR_LEFT
- : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
+ throw new UnsupportedOperationException("enterSplitScreen not implemented by starter");
}
@Override
public void exitSplitScreen() {
- mSplitScreen.exitSplitScreen();
+ throw new UnsupportedOperationException("exitSplitScreen not implemented by starter");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 1da72f8..04d1264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -89,6 +89,7 @@
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getToken());
if (mAnimationController.isAnimatorsConsumed()) {
+ resetWindowsOffsetInternal(animator.getTransitionDirection());
finishOffset(animator.getDestinationOffset(),
animator.getTransitionDirection());
}
@@ -99,6 +100,7 @@
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getToken());
if (mAnimationController.isAnimatorsConsumed()) {
+ resetWindowsOffsetInternal(animator.getTransitionDirection());
finishOffset(animator.getDestinationOffset(),
animator.getTransitionDirection());
}
@@ -205,6 +207,16 @@
applyTransaction(wct);
}
+ private void resetWindowsOffsetInternal(
+ @OneHandedAnimationController.TransitionDirection int td) {
+ if (td == TRANSITION_DIRECTION_TRIGGER) {
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ resetWindowsOffset(wct);
+ applyTransaction(wct);
+ }
+
private void resetWindowsOffset(WindowContainerTransaction wct) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 90992fb..45aa387 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -21,6 +21,7 @@
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.app.TaskInfo;
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.SurfaceControl;
@@ -99,18 +100,20 @@
@SuppressWarnings("unchecked")
@VisibleForTesting
- public PipTransitionAnimator getAnimator(SurfaceControl leash,
+ public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
Rect destinationBounds, float alphaStart, float alphaEnd) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
+ PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
+ alphaEnd));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
mCurrentAnimator.updateEndValue(alphaEnd);
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
+ PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
+ alphaEnd));
}
return mCurrentAnimator;
}
@@ -131,13 +134,13 @@
* the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
*/
@VisibleForTesting
- public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect baseBounds,
- Rect startBounds, Rect endBounds, Rect sourceHintRect,
+ public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
+ Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, float startingAngle) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, startBounds, endBounds,
- sourceHintRect, direction, 0 /* startingAngle */));
+ PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
+ endBounds, sourceHintRect, direction, 0 /* startingAngle */));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -152,8 +155,8 @@
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, baseBounds, startBounds, endBounds,
- sourceHintRect, direction, startingAngle));
+ PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
+ endBounds, sourceHintRect, direction, startingAngle));
}
return mCurrentAnimator;
}
@@ -177,18 +180,18 @@
/**
* Called when PiP animation is started.
*/
- public void onPipAnimationStart(PipTransitionAnimator animator) {}
+ public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {}
/**
* Called when PiP animation is ended.
*/
- public void onPipAnimationEnd(SurfaceControl.Transaction tx,
+ public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
PipTransitionAnimator animator) {}
/**
* Called when PiP animation is cancelled.
*/
- public void onPipAnimationCancel(PipTransitionAnimator animator) {}
+ public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {}
}
/**
@@ -198,6 +201,7 @@
public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
ValueAnimator.AnimatorUpdateListener,
ValueAnimator.AnimatorListener {
+ private final TaskInfo mTaskInfo;
private final SurfaceControl mLeash;
private final @AnimationType int mAnimationType;
private final Rect mDestinationBounds = new Rect();
@@ -213,9 +217,10 @@
private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private @TransitionDirection int mTransitionDirection;
- private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
- Rect destinationBounds, T baseValue, T startValue, T endValue,
- float startingAngle) {
+ private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
+ @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
+ T endValue, float startingAngle) {
+ mTaskInfo = taskInfo;
mLeash = leash;
mAnimationType = animationType;
mDestinationBounds.set(destinationBounds);
@@ -234,7 +239,7 @@
mCurrentValue = mStartValue;
onStartTransaction(mLeash, newSurfaceControlTransaction());
if (mPipAnimationCallback != null) {
- mPipAnimationCallback.onPipAnimationStart(this);
+ mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
}
}
@@ -250,14 +255,14 @@
final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
onEndTransaction(mLeash, tx, mTransitionDirection);
if (mPipAnimationCallback != null) {
- mPipAnimationCallback.onPipAnimationEnd(tx, this);
+ mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
}
}
@Override
public void onAnimationCancel(Animator animation) {
if (mPipAnimationCallback != null) {
- mPipAnimationCallback.onPipAnimationCancel(this);
+ mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this);
}
}
@@ -368,9 +373,9 @@
abstract void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction);
- static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
+ static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash,
Rect destinationBounds, float startValue, float endValue) {
- return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
+ return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA,
destinationBounds, startValue, startValue, endValue, 0) {
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
@@ -403,7 +408,7 @@
};
}
- static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
+ static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, float startingAngle) {
// Just for simplicity we'll interpolate between the source rect hint insets and empty
@@ -427,7 +432,7 @@
final Rect sourceInsets = new Rect(0, 0, 0, 0);
// construct new Rect instances in case they are recycled
- return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
+ return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue),
startingAngle) {
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index a8961ea..ac5d14c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -19,7 +19,9 @@
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.NonNull;
+import android.app.PictureInPictureParams;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
@@ -27,7 +29,6 @@
import android.util.DisplayMetrics;
import android.util.Size;
import android.util.TypedValue;
-import android.view.DisplayInfo;
import android.view.Gravity;
import com.android.wm.shell.common.DisplayLayout;
@@ -142,11 +143,53 @@
true /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
}
+ /**
+ *
+ * Get the smallest/most minimal size allowed.
+ */
+ public Size getMinimalSize(ActivityInfo activityInfo) {
+ if (activityInfo == null || activityInfo.windowLayout == null) {
+ return null;
+ }
+ final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
+ // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
+ // without minWidth/minHeight
+ if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
+ return new Size(windowLayout.minWidth, windowLayout.minHeight);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the source hint rect if it is valid (if provided and is contained by the current
+ * task bounds).
+ */
+ public static Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds) {
+ final Rect sourceHintRect = params != null && params.hasSourceBoundsHint()
+ ? params.getSourceRectHint()
+ : null;
+ if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
+ return sourceHintRect;
+ }
+ return null;
+ }
+
public float getDefaultAspectRatio() {
return mDefaultAspectRatio;
}
/**
+ *
+ * Give the aspect ratio if the supplied PiP params have one, or else return default.
+ */
+ public float getAspectRatioOrDefault(
+ @android.annotation.Nullable PictureInPictureParams params) {
+ return params != null && params.hasSetAspectRatio()
+ ? params.getAspectRatio()
+ : getDefaultAspectRatio();
+ }
+
+ /**
* @return whether the given {@param aspectRatio} is valid.
*/
private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index b112c51..cb39b4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -25,7 +25,6 @@
import android.graphics.Rect;
import android.util.Size;
import android.view.Display;
-import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.TriConsumer;
@@ -344,6 +343,16 @@
}
}
+ /**
+ * Initialize states when first entering PiP.
+ */
+ public void setBoundsStateForEntry(ComponentName componentName, float aspectRatio,
+ Size overrideMinSize) {
+ setLastPipComponentName(componentName);
+ setAspectRatio(aspectRatio);
+ setOverrideMinSize(overrideMinSize);
+ }
+
/** Returns whether the shelf is currently showing. */
public boolean isShelfShowing() {
return mIsShelfShowing;
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 b7958b7..fb83006 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
@@ -41,10 +41,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -54,7 +54,6 @@
import android.os.RemoteException;
import android.util.Log;
import android.util.Rational;
-import android.util.Size;
import android.view.Display;
import android.view.SurfaceControl;
import android.window.TaskOrganizer;
@@ -63,7 +62,6 @@
import android.window.WindowContainerTransactionCallback;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -71,10 +69,10 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.transition.Transitions;
+
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -132,8 +130,8 @@
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
+ private final PipTransitionController mPipTransitionController;
private final PipUiEventLogger mPipUiEventLoggerLogger;
- private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
@@ -145,7 +143,8 @@
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
@Override
- public void onPipAnimationStart(PipAnimationController.PipTransitionAnimator animator) {
+ public void onPipAnimationStart(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
if (direction == TRANSITION_DIRECTION_TO_PIP) {
// TODO (b//169221267): Add jank listener for transactions without buffer updates.
@@ -156,7 +155,7 @@
}
@Override
- public void onPipAnimationEnd(SurfaceControl.Transaction tx,
+ public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
finishResize(tx, animator.getDestinationBounds(), direction,
@@ -170,7 +169,8 @@
}
@Override
- public void onPipAnimationCancel(PipAnimationController.PipTransitionAnimator animator) {
+ public void onPipAnimationCancel(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
sendOnPipTransitionCancelled(animator.getTransitionDirection());
}
};
@@ -202,7 +202,9 @@
public PipTaskOrganizer(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
+ @NonNull PipAnimationController pipAnimationController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
+ @NonNull PipTransitionController pipTransitionController,
Optional<LegacySplitScreen> splitScreenOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@@ -211,10 +213,11 @@
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
+ mPipTransitionController = pipTransitionController;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = surfaceTransactionHelper;
- mPipAnimationController = new PipAnimationController(mSurfaceTransactionHelper);
+ mPipAnimationController = pipAnimationController;
mPipUiEventLoggerLogger = pipUiEventLogger;
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mSplitScreenOptional = splitScreenOptional;
@@ -246,13 +249,6 @@
}
/**
- * Registers {@link PipTransitionCallback} to receive transition callbacks.
- */
- public void registerPipTransitionCallback(PipTransitionCallback callback) {
- mPipTransitionCallbacks.add(callback);
- }
-
- /**
* Registers a callback when a display change has been detected when we enter PiP.
*/
public void registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback) {
@@ -275,7 +271,7 @@
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams) {
mInSwipePipToHomeTransition = true;
- sendOnPipTransitionStarted(componentName, TRANSITION_DIRECTION_TO_PIP);
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
// disable the conflicting transaction from fixed rotation, see also
// onFixedRotationStarted and onFixedRotationFinished
@@ -296,9 +292,9 @@
private void setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params,
ActivityInfo activityInfo) {
- mPipBoundsState.setLastPipComponentName(componentName);
- mPipBoundsState.setAspectRatio(getAspectRatioOrDefault(params));
- mPipBoundsState.setOverrideMinSize(getMinimalSize(activityInfo));
+ mPipBoundsState.setBoundsStateForEntry(componentName,
+ mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
+ mPipBoundsAlgorithm.getMinimalSize(activityInfo));
}
/**
@@ -362,8 +358,8 @@
t.apply();
// Make sure to grab the latest source hint rect as it could have been
// updated right after applying the windowing mode change.
- final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams,
- destinationBounds);
+ final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+ mPictureInPictureParams, destinationBounds);
scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
0 /* startingAngle */, sourceHintRect, direction,
animationDurationMs, null /* updateBoundsCallback */);
@@ -398,7 +394,7 @@
// removePipImmediately is expected when the following animation finishes.
mPipAnimationController
- .getAnimator(mLeash, mPipBoundsState.getBounds(), 1f, 0f)
+ .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), 1f, 0f)
.setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
@@ -470,10 +466,17 @@
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ mPipMenuController.attach(mLeash);
+ }
+ return;
+ }
+
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
mPipMenuController.attach(mLeash);
- final Rect sourceHintRect = getValidSourceHintRect(info.pictureInPictureParams,
- currentBounds);
+ final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+ info.pictureInPictureParams, currentBounds);
scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
null /* updateBoundsCallback */);
@@ -486,21 +489,6 @@
}
}
- /**
- * Returns the source hint rect if it is valid (if provided and is contained by the current
- * task bounds).
- */
- private Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds) {
- final Rect sourceHintRect = params != null
- && params.hasSourceBoundsHint()
- ? params.getSourceRectHint()
- : null;
- if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
- return sourceHintRect;
- }
- return null;
- }
-
@VisibleForTesting
void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
// If we are fading the PIP in, then we should move the pip to the final location as
@@ -512,7 +500,7 @@
tx.apply();
applyEnterPipSyncTransaction(destinationBounds, () -> {
mPipAnimationController
- .getAnimator(mLeash, destinationBounds, 0f, 1f)
+ .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
@@ -547,19 +535,10 @@
private void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
- sendOnPipTransitionStarted(mTaskInfo.baseActivity, direction);
- }
-
- private void sendOnPipTransitionStarted(ComponentName componentName,
- @PipAnimationController.TransitionDirection int direction) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
mState = State.ENTERING_PIP;
}
- final Rect pipBounds = mPipBoundsState.getBounds();
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionStarted(componentName, direction, pipBounds);
- }
+ mPipTransitionController.sendOnPipTransitionStarted(direction);
}
private void sendOnPipTransitionFinished(
@@ -567,18 +546,12 @@
if (direction == TRANSITION_DIRECTION_TO_PIP) {
mState = State.ENTERED_PIP;
}
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction);
- }
+ mPipTransitionController.sendOnPipTransitionFinished(direction);
}
private void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction);
- }
+ mPipTransitionController.sendOnPipTransitionCancelled(direction);
}
/**
@@ -616,7 +589,8 @@
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
mPipBoundsState.setLastPipComponentName(info.topActivity);
- mPipBoundsState.setOverrideMinSize(getMinimalSize(info.topActivityInfo));
+ mPipBoundsState.setOverrideMinSize(
+ mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo));
final PictureInPictureParams newParams = info.pictureInPictureParams;
if (newParams == null || !applyPictureInPictureParams(newParams)) {
Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
@@ -1081,33 +1055,14 @@
Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
? mPipBoundsState.getBounds() : currentBounds;
mPipAnimationController
- .getAnimator(mLeash, baseBounds, currentBounds, destinationBounds, sourceHintRect,
- direction, startingAngle)
+ .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
+ sourceHintRect, direction, startingAngle)
.setTransitionDirection(direction)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
.start();
}
- private Size getMinimalSize(ActivityInfo activityInfo) {
- if (activityInfo == null || activityInfo.windowLayout == null) {
- return null;
- }
- final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
- // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
- // without minWidth/minHeight
- if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
- return new Size(windowLayout.minWidth, windowLayout.minHeight);
- }
- return null;
- }
-
- private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) {
- return params == null || !params.hasSetAspectRatio()
- ? mPipBoundsAlgorithm.getDefaultAspectRatio()
- : params.getAspectRatio();
- }
-
/**
* Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
*
@@ -1157,24 +1112,4 @@
public String toString() {
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_PIP);
}
-
- /**
- * Callback interface for PiP transitions (both from and to PiP mode)
- */
- public interface PipTransitionCallback {
- /**
- * Callback when the pip transition is started.
- */
- void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds);
-
- /**
- * Callback when the pip transition is finished.
- */
- void onPipTransitionFinished(ComponentName activity, int direction);
-
- /**
- * Callback when the pip transition is cancelled.
- */
- void onPipTransitionCanceled(ComponentName activity, int direction);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
new file mode 100644
index 0000000..91e8c99
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -0,0 +1,162 @@
+/*
+ * 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.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
+import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and
+ * exit animation.
+ */
+public class PipTransition extends PipTransitionController {
+
+ private final int mEnterExitAnimationDuration;
+ private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ private Transitions.TransitionFinishCallback mFinishCallback;
+
+ public PipTransition(Context context,
+ PipBoundsState pipBoundsState, PipMenuController pipMenuController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipAnimationController pipAnimationController,
+ Transitions transitions,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+ super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
+ pipAnimationController, transitions, shellTaskOrganizer);
+ mEnterExitAnimationDuration = context.getResources()
+ .getInteger(R.integer.config_pipResizeAnimationDuration);
+ }
+
+ @Override
+ public boolean startAnimation(@android.annotation.NonNull IBinder transition,
+ @android.annotation.NonNull TransitionInfo info,
+ @android.annotation.NonNull SurfaceControl.Transaction t,
+ @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_PINNED) {
+ mFinishCallback = finishCallback;
+ return startEnterAnimation(change.getTaskInfo(), change.getLeash(), t);
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction,
+ SurfaceControl.Transaction tx) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareFinishResizeTransaction(taskInfo, destinationBounds,
+ direction, tx, wct);
+ mFinishCallback.onTransitionFinished(wct, null);
+ finishResizeForMenu(destinationBounds);
+ }
+
+ private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
+ final SurfaceControl.Transaction t) {
+ setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
+ taskInfo.topActivityInfo);
+ final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ PipAnimationController.PipTransitionAnimator animator;
+ if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ final Rect sourceHintRect =
+ PipBoundsAlgorithm.getValidSourceHintRect(
+ taskInfo.pictureInPictureParams, currentBounds);
+ animator = mPipAnimationController.getAnimator(taskInfo, leash,
+ currentBounds, currentBounds, destinationBounds, sourceHintRect,
+ TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */);
+ } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ t.setAlpha(leash, 0f);
+ t.apply();
+ animator = mPipAnimationController.getAnimator(taskInfo, leash,
+ destinationBounds, 0f, 1f);
+ mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ } else {
+ throw new RuntimeException("Unrecognized animation type: "
+ + mOneShotAnimationType);
+ }
+ animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(mEnterExitAnimationDuration)
+ .start();
+ return true;
+ }
+
+ private void finishResizeForMenu(Rect destinationBounds) {
+ mPipMenuController.movePipMenu(null, null, destinationBounds);
+ mPipMenuController.updateMenuBounds(destinationBounds);
+ }
+
+ private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction,
+ SurfaceControl.Transaction tx,
+ WindowContainerTransaction wct) {
+ Rect taskBounds = null;
+ if (isInPipDirection(direction)) {
+ // If we are animating from fullscreen using a bounds animation, then reset the
+ // activity windowing mode set by WM, and set the task bounds to the final bounds
+ taskBounds = destinationBounds;
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
+ } else if (isOutPipDirection(direction)) {
+ // If we are animating to fullscreen, then we need to reset the override bounds
+ // on the task to ensure that the task "matches" the parent's bounds.
+ taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
+ ? null : destinationBounds;
+ wct.setWindowingMode(taskInfo.token, getOutPipWindowingMode());
+ // Simply reset the activity mode set prior to the animation running.
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+
+ wct.setBounds(taskInfo.token, taskBounds);
+ wct.setBoundsChangeTransaction(taskInfo.token, tx);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
new file mode 100644
index 0000000..d801c91
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -0,0 +1,184 @@
+/*
+ * 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.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+
+import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Responsible supplying PiP Transitions.
+ */
+public abstract class PipTransitionController implements Transitions.TransitionHandler {
+
+ protected final PipAnimationController mPipAnimationController;
+ protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ protected final PipBoundsState mPipBoundsState;
+ protected final ShellTaskOrganizer mShellTaskOrganizer;
+ protected final PipMenuController mPipMenuController;
+ private final Handler mMainHandler;
+ private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+
+ protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
+ new PipAnimationController.PipAnimationCallback() {
+ @Override
+ public void onPipAnimationStart(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ // TODO (b//169221267): Add jank listener for transactions without buffer
+ // updates.
+ //InteractionJankMonitor.getInstance().begin(
+ // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000);
+ }
+ sendOnPipTransitionStarted(direction);
+ }
+
+ @Override
+ public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ mPipBoundsState.setBounds(animator.getDestinationBounds());
+ if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
+ return;
+ }
+ onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
+ sendOnPipTransitionFinished(direction);
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ // TODO (b//169221267): Add jank listener for transactions without buffer
+ // updates.
+ //InteractionJankMonitor.getInstance().end(
+ // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
+ }
+ }
+
+ @Override
+ public void onPipAnimationCancel(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
+ sendOnPipTransitionCancelled(animator.getTransitionDirection());
+ }
+ };
+
+ /**
+ * Called when transition is about to finish. This is usually for performing tasks such as
+ * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework.
+ */
+ public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction,
+ SurfaceControl.Transaction tx) {
+ }
+
+ public PipTransitionController(PipBoundsState pipBoundsState,
+ PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipAnimationController pipAnimationController, Transitions transitions,
+ @android.annotation.NonNull ShellTaskOrganizer shellTaskOrganizer) {
+ mPipBoundsState = pipBoundsState;
+ mPipMenuController = pipMenuController;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipAnimationController = pipAnimationController;
+ mMainHandler = new Handler(Looper.getMainLooper());
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.addHandler(this);
+ }
+ }
+
+ /**
+ * Registers {@link PipTransitionCallback} to receive transition callbacks.
+ */
+ public void registerPipTransitionCallback(PipTransitionCallback callback) {
+ mPipTransitionCallbacks.add(callback);
+ }
+
+ protected void sendOnPipTransitionStarted(
+ @PipAnimationController.TransitionDirection int direction) {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionStarted(direction, pipBounds);
+ }
+ }
+
+ protected void sendOnPipTransitionFinished(
+ @PipAnimationController.TransitionDirection int direction) {
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionFinished(direction);
+ }
+ }
+
+ protected void sendOnPipTransitionCancelled(
+ @PipAnimationController.TransitionDirection int direction) {
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionCanceled(direction);
+ }
+ }
+
+ /**
+ * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
+ * and can be overridden to restore to an alternate windowing mode.
+ */
+ public int getOutPipWindowingMode() {
+ // By default, simply reset the windowing mode to undefined.
+ return WINDOWING_MODE_UNDEFINED;
+ }
+
+ protected void setBoundsStateForEntry(ComponentName componentName,
+ PictureInPictureParams params,
+ ActivityInfo activityInfo) {
+ mPipBoundsState.setBoundsStateForEntry(componentName,
+ mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
+ mPipBoundsAlgorithm.getMinimalSize(activityInfo));
+ }
+
+ /**
+ * Callback interface for PiP transitions (both from and to PiP mode)
+ */
+ public interface PipTransitionCallback {
+ /**
+ * Callback when the pip transition is started.
+ */
+ void onPipTransitionStarted(int direction, Rect pipBounds);
+
+ /**
+ * Callback when the pip transition is finished.
+ */
+ void onPipTransitionFinished(int direction);
+
+ /**
+ * Callback when the pip transition is cancelled.
+ */
+ void onPipTransitionCanceled(int direction);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 4b118f1..a57eee8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -212,8 +212,8 @@
}
@Nullable
- Size getEstimatedMenuSize() {
- return mPipMenuView == null ? null : mPipMenuView.getEstimatedMenuSize();
+ Size getEstimatedMinMenuSize() {
+ return mPipMenuView == null ? null : mPipMenuView.getEstimatedMinMenuSize();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index c06f9d0..c3970e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -61,6 +61,7 @@
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUtils;
import java.io.PrintWriter;
@@ -69,7 +70,7 @@
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
-public class PipController implements PipTaskOrganizer.PipTransitionCallback {
+public class PipController implements PipTransitionController.PipTransitionCallback {
private static final String TAG = "PipController";
private Context mContext;
@@ -82,6 +83,7 @@
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipBoundsState mPipBoundsState;
private PipTouchHandler mTouchHandler;
+ private PipTransitionController mPipTransitionController;
protected final PipImpl mImpl = new PipImpl();
private final Rect mTmpInsetBounds = new Rect();
@@ -214,7 +216,6 @@
}
}
-
/**
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@@ -223,7 +224,8 @@
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
- PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
+ PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+ WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
Slog.w(TAG, "Device doesn't support Pip feature");
@@ -232,7 +234,8 @@
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor)
+ pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+ taskStackListener, mainExecutor)
.mImpl;
}
@@ -245,6 +248,7 @@
PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler,
+ PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
ShellExecutor mainExecutor
@@ -266,9 +270,10 @@
mMenuController = phonePipMenuController;
mTouchHandler = pipTouchHandler;
mAppOpsListener = pipAppOpsListener;
+ mPipTransitionController = pipTransitionController;
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mainExecutor);
- mPipTaskOrganizer.registerPipTransitionCallback(this);
+ mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipBoundsState.setDisplayId(displayId);
onDisplayChanged(displayController.getDisplayLayout(displayId),
@@ -489,7 +494,7 @@
}
@Override
- public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
+ public void onPipTransitionStarted(int direction, Rect pipBounds) {
if (isOutPipDirection(direction)) {
// Exiting PIP, save the reentry state to restore to when re-entering.
saveReentryState(pipBounds);
@@ -514,12 +519,12 @@
}
@Override
- public void onPipTransitionFinished(ComponentName activity, int direction) {
+ public void onPipTransitionFinished(int direction) {
onPipTransitionFinishedOrCanceled(direction);
}
@Override
- public void onPipTransitionCanceled(ComponentName activity, int direction) {
+ public void onPipTransitionCanceled(int direction) {
onPipTransitionFinishedOrCanceled(direction);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 48942b6..962c467 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -231,7 +231,6 @@
&& (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
mAllowTouches = !disallowTouchesUntilAnimationEnd;
cancelDelayedHide();
- updateActionViews(stackBounds);
if (mMenuContainerAnimator != null) {
mMenuContainerAnimator.cancel();
}
@@ -280,6 +279,7 @@
setVisibility(VISIBLE);
mMenuContainerAnimator.start();
}
+ updateActionViews(stackBounds);
} else {
// If we are already visible, then just start the delayed dismiss and unregister any
// existing input consumers from the previous drag
@@ -367,15 +367,17 @@
}
/**
- * @return estimated {@link Size} for which the width is based on number of actions and
- * height based on the height of expand button + top and bottom action bar.
+ * @return Estimated minimum {@link Size} to hold the actions.
+ * See also {@link #updateActionViews(Rect)}
*/
- Size getEstimatedMenuSize() {
- final int pipActionSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_action_size);
- final int width = mActions.size() * pipActionSize;
- final int height = pipActionSize * 2 + mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_expand_action_size);
+ Size getEstimatedMinMenuSize() {
+ final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
+ // the minimum width would be (2 * pipActionSize) since we have settings and dismiss button
+ // on the top action container.
+ final int width = Math.max(2, mActions.size()) * pipActionSize;
+ final int height = getResources().getDimensionPixelSize(R.dimen.pip_expand_action_size)
+ + getResources().getDimensionPixelSize(R.dimen.pip_action_padding)
+ + getResources().getDimensionPixelSize(R.dimen.pip_expand_container_edge_margin);
return new Size(width, height);
}
@@ -393,7 +395,7 @@
return true;
});
- if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
+ if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE || mMenuState == MENU_STATE_NONE) {
actionsContainer.setVisibility(View.INVISIBLE);
} else {
actionsContainer.setVisibility(View.VISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 4df7cef..b19dcae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -22,12 +22,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
-import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Choreographer;
@@ -45,6 +43,7 @@
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import java.util.function.Consumer;
@@ -62,6 +61,7 @@
private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+ private static final int UNSTASH_DURATION = 250;
private static final int LEAVE_PIP_DURATION = 300;
private static final int SHIFT_DURATION = 300;
@@ -152,13 +152,13 @@
*/
private Runnable mPostPipTransitionCallback;
- private final PipTaskOrganizer.PipTransitionCallback mPipTransitionCallback =
- new PipTaskOrganizer.PipTransitionCallback() {
+ private final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
+ new PipTransitionController.PipTransitionCallback() {
@Override
- public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {}
+ public void onPipTransitionStarted(int direction, Rect pipBounds) {}
@Override
- public void onPipTransitionFinished(ComponentName activity, int direction) {
+ public void onPipTransitionFinished(int direction) {
if (mPostPipTransitionCallback != null) {
mPostPipTransitionCallback.run();
mPostPipTransitionCallback = null;
@@ -166,20 +166,20 @@
}
@Override
- public void onPipTransitionCanceled(ComponentName activity, int direction) {}
+ public void onPipTransitionCanceled(int direction) {}
};
public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
- PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator,
- ShellExecutor mainExecutor) {
+ PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
+ FloatingContentCoordinator floatingContentCoordinator, ShellExecutor mainExecutor) {
mContext = context;
mPipTaskOrganizer = pipTaskOrganizer;
mPipBoundsState = pipBoundsState;
mMenuController = menuController;
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
- mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
@@ -482,6 +482,13 @@
}
/**
+ * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+ */
+ void animateToUnStashedBounds(Rect unstashedBounds) {
+ resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+ }
+
+ /**
* Animates the PiP to offset it from the IME or shelf.
*/
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 128d13c..e69c6f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.pip.phone;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
@@ -30,7 +32,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Handler;
import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Size;
@@ -50,6 +51,7 @@
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
@@ -62,9 +64,8 @@
*/
public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
-
- private static final float STASH_MINIMUM_VELOCITY_X = 3000.f;
private static final float MINIMUM_SIZE_PERCENT = 0.4f;
+ private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
// Allow PIP to resize to a slightly bigger state upon touch
private final boolean mEnableResize;
@@ -87,6 +88,8 @@
*/
private boolean mEnableStash = true;
+ private float mStashVelocityThreshold;
+
// The reference inset bounds, used to determine the dismiss fraction
private final Rect mInsetBounds = new Rect();
private int mExpandedShortestEdgeSize;
@@ -154,6 +157,7 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor) {
@@ -166,7 +170,7 @@
mMenuController.addListener(new PipMenuListener());
mGesture = new DefaultPipTouchGesture();
mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
- mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(),
+ mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), pipTransitionController,
floatingContentCoordinator, mainExecutor);
mPipResizeGestureHandler =
new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
@@ -176,9 +180,17 @@
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
- () -> mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
- mPipBoundsState.getBounds(), true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle()),
+ () -> {
+ if (mPipBoundsState.isStashed()) {
+ animateToUnStashedState();
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ } else {
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ },
menuController::hideMenu,
mainExecutor);
@@ -205,6 +217,19 @@
PIP_STASHING, /* defaultValue = */ true);
}
});
+ mStashVelocityThreshold = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+ mStashVelocityThreshold = properties.getFloat(
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ }
+ });
}
private void reloadResources() {
@@ -710,6 +735,17 @@
mSavedSnapFraction = -1f;
}
+ private void animateToUnStashedState() {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+ final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+ unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+ : mInsetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+ : mInsetBounds.right;
+ mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+ }
+
/**
* @return the motion helper.
*/
@@ -773,7 +809,7 @@
}
if (touchState.startedDragging()) {
- mPipBoundsState.setStashed(PipBoundsState.STASH_TYPE_NONE);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
mSavedSnapFraction = -1f;
mPipDismissTargetHandler.showDismissTargetMaybe();
}
@@ -826,14 +862,8 @@
// Reset the touch state on up before the fling settles
mTouchState.reset();
- // If user flings the PIP window above the minimum velocity, stash PIP.
- // Only allow stashing to the edge if the user starts dragging the PIP from that
- // edge.
if (mEnableStash && !mPipBoundsState.isStashed()
- && ((vel.x > STASH_MINIMUM_VELOCITY_X
- && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
- || (vel.x < -STASH_MINIMUM_VELOCITY_X
- && mDownSavedFraction > 3f && mDownSavedFraction < 4f))) {
+ && shouldStash(vel, getPossiblyMotionBounds())) {
mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */);
} else {
mMotionHelper.flingToSnapTarget(vel.x, vel.y,
@@ -847,24 +877,29 @@
&& mPipBoundsState.getBounds().height()
< mPipBoundsState.getMaxSize().y;
if (toExpand) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
animateToMaximizedState(null);
} else {
- animateToMinimizedState();
+ animateToUnexpandedState(getUserResizeBounds());
}
- mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
} else {
// Expand to fullscreen if this is a double tap
// the PiP should be frozen until the transition ends
setTouchEnabled(false);
mMotionHelper.expandLeavePip();
}
- } else if (mMenuState != MENU_STATE_FULL && !mPipBoundsState.isStashed()) {
+ } else if (mMenuState != MENU_STATE_FULL) {
if (!mTouchState.isWaitingForDoubleTap()) {
- // User has stalled long enough for this not to be a drag or a double tap, just
- // expand the menu
- mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle());
+ if (mPipBoundsState.isStashed()) {
+ animateToUnStashedState();
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ } else {
+ // User has stalled long enough for this not to be a drag or a double tap,
+ // just expand the menu
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
} else {
// Next touch event _may_ be the second tap for the double-tap, schedule a
// fallback runnable to trigger the menu if no touch event occurs before the
@@ -895,6 +930,26 @@
mPipExclusionBoundsChangeListener.get().accept(new Rect());
}
}
+
+ private boolean shouldStash(PointF vel, Rect motionBounds) {
+ // If user flings the PIP window above the minimum velocity, stash PIP.
+ // Only allow stashing to the edge if the user starts dragging the PIP from the
+ // opposite edge.
+ final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold
+ && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
+ || (vel.x > mStashVelocityThreshold
+ && mDownSavedFraction > 3f && mDownSavedFraction < 4f));
+
+ // If User releases the PIP window while it's out of the display bounds, put
+ // PIP into stashed mode.
+ final int offset = motionBounds.width() / 2;
+ final boolean stashFromDroppingOnEdge =
+ (motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset
+ || motionBounds.left
+ < mPipBoundsState.getDisplayBounds().left - offset);
+
+ return stashFromFlingToEdge || stashFromDroppingOnEdge;
+ }
}
void setPipExclusionBoundsChangeListener(Consumer<Rect> pipExclusionBoundsChangeListener) {
@@ -932,14 +987,14 @@
if (!mEnableResize) {
return false;
}
- final Size estimatedMenuSize = mMenuController.getEstimatedMenuSize();
- if (estimatedMenuSize == null) {
+ final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+ if (estimatedMinMenuSize == null) {
Log.wtf(TAG, "Failed to get estimated menu size");
return false;
}
final Rect currentBounds = mPipBoundsState.getBounds();
- return currentBounds.width() < estimatedMenuSize.getWidth()
- || currentBounds.height() < estimatedMenuSize.getHeight();
+ return currentBounds.width() < estimatedMinMenuSize.getWidth()
+ || currentBounds.height() < estimatedMinMenuSize.getHeight();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 75fc9f5..56f183f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -46,6 +46,7 @@
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,7 +54,7 @@
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
-public class TvPipController implements PipTaskOrganizer.PipTransitionCallback,
+public class TvPipController implements PipTransitionController.PipTransitionCallback,
TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
private static final String TAG = "TvPipController";
static final boolean DEBUG = true;
@@ -105,6 +106,7 @@
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
@@ -116,6 +118,7 @@
pipBoundsState,
pipBoundsAlgorithm,
pipTaskOrganizer,
+ pipTransitionController,
tvPipMenuController,
pipMediaController,
pipNotificationController,
@@ -129,6 +132,7 @@
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
@@ -152,7 +156,7 @@
mTvPipMenuController.setDelegate(this);
mPipTaskOrganizer = pipTaskOrganizer;
- mPipTaskOrganizer.registerPipTransitionCallback(this);
+ pipTransitionController.registerPipTransitionCallback(this);
loadConfigurations();
@@ -302,17 +306,17 @@
}
@Override
- public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
+ public void onPipTransitionStarted(int direction, Rect pipBounds) {
if (DEBUG) Log.d(TAG, "onPipTransition_Started(), state=" + stateToName(mState));
}
@Override
- public void onPipTransitionCanceled(ComponentName activity, int direction) {
+ public void onPipTransitionCanceled(int direction) {
if (DEBUG) Log.d(TAG, "onPipTransition_Canceled(), state=" + stateToName(mState));
}
@Override
- public void onPipTransitionFinished(ComponentName activity, int direction) {
+ public void onPipTransitionFinished(int direction) {
if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
if (mState == STATE_PIP_MENU) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
new file mode 100644
index 0000000..b7caf72
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.app.TaskInfo;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * PiP Transition for TV.
+ * TODO: Implement animation once TV is using Transitions.
+ */
+public class TvPipTransition extends PipTransitionController {
+ public TvPipTransition(PipBoundsState pipBoundsState,
+ PipMenuController pipMenuController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipAnimationController pipAnimationController,
+ Transitions transitions,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+ super(pipBoundsState, pipMenuController, pipBoundsAlgorithm, pipAnimationController,
+ transitions, shellTaskOrganizer);
+ }
+
+ @Override
+ public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, int direction,
+ SurfaceControl.Transaction tx) {
+
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
new file mode 100644
index 0000000..e47e1ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -0,0 +1,172 @@
+/*
+ * 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.app.ActivityClient;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import com.android.wm.shell.R;
+
+/** Button to restart the size compat activity. */
+class SizeCompatRestartButton extends ImageButton implements View.OnClickListener,
+ View.OnLongClickListener {
+ private static final String TAG = "SizeCompatRestartButton";
+
+ final WindowManager.LayoutParams mWinParams;
+ final boolean mShouldShowHint;
+ final int mDisplayId;
+ final int mPopupOffsetX;
+ final int mPopupOffsetY;
+
+ private IBinder mLastActivityToken;
+ private PopupWindow mShowingHint;
+
+ SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) {
+ super(context);
+ mDisplayId = displayId;
+ mShouldShowHint = !hasShownHint;
+ final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button);
+ setImageDrawable(drawable);
+ setContentDescription(context.getString(R.string.restart_button_description));
+
+ final int drawableW = drawable.getIntrinsicWidth();
+ final int drawableH = drawable.getIntrinsicHeight();
+ mPopupOffsetX = drawableW / 2;
+ mPopupOffsetY = drawableH * 2;
+
+ final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
+ final GradientDrawable mask = new GradientDrawable();
+ mask.setShape(GradientDrawable.OVAL);
+ mask.setColor(color);
+ setBackground(new RippleDrawable(color, null /* content */, mask));
+ setOnClickListener(this);
+ setOnLongClickListener(this);
+
+ mWinParams = new WindowManager.LayoutParams();
+ mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
+ mWinParams.width = drawableW * 2;
+ mWinParams.height = drawableH * 2;
+ mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ mWinParams.format = PixelFormat.TRANSLUCENT;
+ mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName()
+ + context.getDisplayId());
+ }
+
+ void updateLastTargetActivity(IBinder activityToken) {
+ mLastActivityToken = activityToken;
+ }
+
+ /** @return {@code false} if the target display is invalid. */
+ boolean show() {
+ try {
+ getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
+ } catch (WindowManager.InvalidDisplayException e) {
+ // The target display may have been removed when the callback has just arrived.
+ Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
+ return false;
+ }
+ return true;
+ }
+
+ void remove() {
+ if (mShowingHint != null) {
+ mShowingHint.dismiss();
+ }
+ getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ showHint();
+ return true;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mShouldShowHint) {
+ showHint();
+ }
+ }
+
+ @Override
+ public void setLayoutDirection(int layoutDirection) {
+ final int gravity = getGravity(layoutDirection);
+ if (mWinParams.gravity != gravity) {
+ mWinParams.gravity = gravity;
+ if (mShowingHint != null) {
+ mShowingHint.dismiss();
+ showHint();
+ }
+ getContext().getSystemService(WindowManager.class).updateViewLayout(this,
+ mWinParams);
+ }
+ super.setLayoutDirection(layoutDirection);
+ }
+
+ void showHint() {
+ if (mShowingHint != null) {
+ return;
+ }
+
+ final View popupView = LayoutInflater.from(getContext()).inflate(
+ R.layout.size_compat_mode_hint, null /* root */);
+ final PopupWindow popupWindow = new PopupWindow(popupView,
+ LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ popupWindow.setWindowLayoutType(mWinParams.type);
+ popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
+ popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
+ popupWindow.setClippingEnabled(false);
+ popupWindow.setOnDismissListener(() -> mShowingHint = null);
+ mShowingHint = popupWindow;
+
+ final Button gotItButton = popupView.findViewById(R.id.got_it);
+ gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
+ null /* content */, null /* mask */));
+ gotItButton.setOnClickListener(view -> popupWindow.dismiss());
+ popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
+ }
+
+ private static int getGravity(int layoutDirection) {
+ return Gravity.BOTTOM
+ | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+ }
+}
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
new file mode 100644
index 0000000..11f22ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
@@ -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.
+ */
+
+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
new file mode 100644
index 0000000..286c3b6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -0,0 +1,191 @@
+/*
+ * 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.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.ShellExecutor;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Shows a restart-activity button on Task when the foreground activity is in size compatibility
+ * mode.
+ */
+public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
+ DisplayImeController.ImePositionProcessor {
+ private static final String TAG = "SizeCompatUI";
+
+ /** The showing buttons by task id. */
+ private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1);
+ /** Avoid creating display context frequently for non-default display. */
+ 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;
+ private final DisplayImeController mImeController;
+
+ /** 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,
+ DisplayController displayController,
+ DisplayImeController imeController,
+ ShellExecutor mainExecutor) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mImeController = imeController;
+ mDisplayController.addDisplayWindowListener(this);
+ mImeController.addPositionProcessor(this);
+ }
+
+ private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+ @Nullable IBinder sizeCompatActivity,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ // TODO Draw button on Task surface
+ if (taskBounds == null || sizeCompatActivity == null || taskListener == null) {
+ // Null token means the current foreground activity is not in size compatibility mode.
+ removeRestartButton(taskId);
+ } else {
+ updateRestartButton(displayId, taskId, sizeCompatActivity);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ mDisplayContextCache.remove(displayId);
+ for (int i = 0; i < mActiveButtons.size(); i++) {
+ final int taskId = mActiveButtons.keyAt(i);
+ final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+ if (button != null && button.mDisplayId == displayId) {
+ removeRestartButton(taskId);
+ }
+ }
+ }
+
+ @Override
+ public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+ final int newVisibility = isShowing ? View.GONE : View.VISIBLE;
+ for (int i = 0; i < mActiveButtons.size(); i++) {
+ final int taskId = mActiveButtons.keyAt(i);
+ final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+ if (button == null || button.mDisplayId != displayId) {
+ continue;
+ }
+
+ // Hide the button when input method is showing.
+ if (button.getVisibility() != newVisibility) {
+ button.setVisibility(newVisibility);
+ }
+ }
+ }
+
+ private void updateRestartButton(int displayId, int taskId, IBinder activityToken) {
+ SizeCompatRestartButton restartButton = mActiveButtons.get(taskId);
+ if (restartButton != null) {
+ restartButton.updateLastTargetActivity(activityToken);
+ return;
+ }
+
+ final Context context = getOrCreateDisplayContext(displayId);
+ if (context == null) {
+ Log.i(TAG, "Cannot get context for display " + displayId);
+ return;
+ }
+
+ restartButton = createRestartButton(context, displayId);
+ restartButton.updateLastTargetActivity(activityToken);
+ if (restartButton.show()) {
+ mActiveButtons.append(taskId, restartButton);
+ } else {
+ onDisplayRemoved(displayId);
+ }
+ }
+
+ @VisibleForTesting
+ SizeCompatRestartButton createRestartButton(Context context, int displayId) {
+ final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId,
+ mHasShownHint);
+ // Only show hint for the first time.
+ mHasShownHint = true;
+ return button;
+ }
+
+ private void removeRestartButton(int taskId) {
+ final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+ if (button != null) {
+ button.remove();
+ mActiveButtons.remove(taskId);
+ }
+ }
+
+ private Context getOrCreateDisplayContext(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ return mContext;
+ }
+ Context context = null;
+ final WeakReference<Context> ref = mDisplayContextCache.get(displayId);
+ if (ref != null) {
+ context = ref.get();
+ }
+ if (context == null) {
+ Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
+ if (display != null) {
+ context = mContext.createDisplayContext(display);
+ mDisplayContextCache.put(displayId, new WeakReference<>(context));
+ }
+ }
+ 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 7c1b9d8..2c68092 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
@@ -18,12 +18,16 @@
import android.annotation.IntDef;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.UserHandle;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import java.io.PrintWriter;
@@ -31,46 +35,93 @@
* Interface to engage split-screen feature.
*/
@ExternalThread
-public interface SplitScreen {
+public interface SplitScreen extends DragAndDropPolicy.Starter {
/**
- * Specifies that the side-stage is positioned at the top half of the screen if
+ * Stage position isn't specified normally meaning to use what ever it is currently set to.
+ */
+ int STAGE_POSITION_UNDEFINED = -1;
+ /**
+ * Specifies that a stage is positioned at the top half of the screen if
* in portrait mode or at the left half of the screen if in landscape mode.
*/
- int SIDE_STAGE_POSITION_TOP_OR_LEFT = 0;
+ int STAGE_POSITION_TOP_OR_LEFT = 0;
/**
- * Specifies that the side-stage is positioned at the bottom half of the screen if
+ * Specifies that a stage is positioned at the bottom half of the screen if
* in portrait mode or at the right half of the screen if in landscape mode.
*/
- int SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+ int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
- @IntDef(prefix = { "SIDE_STAGE_POSITION_" }, value = {
- SIDE_STAGE_POSITION_TOP_OR_LEFT,
- SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT
+ @IntDef(prefix = { "STAGE_POSITION_" }, value = {
+ STAGE_POSITION_UNDEFINED,
+ STAGE_POSITION_TOP_OR_LEFT,
+ STAGE_POSITION_BOTTOM_OR_RIGHT
})
- @interface SideStagePosition {}
+ @interface StagePosition {}
+
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ * @see MainStage
+ */
+ int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ * @see SideStage
+ */
+ int STAGE_TYPE_SIDE = 1;
+
+ @IntDef(prefix = { "STAGE_TYPE_" }, value = {
+ STAGE_TYPE_UNDEFINED,
+ STAGE_TYPE_MAIN,
+ STAGE_TYPE_SIDE
+ })
+ @interface StageType {}
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ interface SplitScreenListener {
+ void onStagePositionChanged(@StageType int stage, @StagePosition int position);
+ void onTaskStageChanged(int taskId, @StageType int stage);
+ }
/** @return {@code true} if split-screen is currently visible. */
boolean isSplitScreenVisible();
/** Moves a task in the side-stage of split-screen. */
- boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition);
+ boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition);
/** Moves a task in the side-stage of split-screen. */
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @SideStagePosition int sideStagePosition);
+ @StagePosition int sideStagePosition);
/** Removes a task from the side-stage of split-screen. */
boolean removeFromSideStage(int taskId);
/** Sets the position of the side-stage. */
- void setSideStagePosition(@SideStagePosition int sideStagePosition);
+ void setSideStagePosition(@StagePosition int sideStagePosition);
/** Hides the side-stage if it is currently visible. */
void setSideStageVisibility(boolean visible);
+ default void enterSplitScreen(int taskId, boolean leftOrTop) {
+ moveToSideStage(taskId,
+ leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT);
+ }
/** Removes the split-screen stages. */
void exitSplitScreen();
/** Gets the stage bounds. */
void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
- /** Updates the launch activity options for the split position we want to launch it in. */
- void updateActivityOptions(Bundle opts, @SideStagePosition int position);
/** Dumps current status of split-screen. */
void dump(@NonNull PrintWriter pw, String prefix);
/** Called when the shell organizer has been registered. */
void onOrganizerRegistered();
+
+ void registerSplitScreenListener(SplitScreenListener listener);
+ void unregisterSplitScreenListener(SplitScreenListener listener);
+
+ void startTask(int taskId,
+ @StageType int stage, @StagePosition int position, @Nullable Bundle options);
+ void startShortcut(String packageName, String shortcutId, @StageType int stage,
+ @StagePosition int position, @Nullable Bundle options, UserHandle user);
+ void startIntent(PendingIntent intent,
+ @StageType int stage, @StagePosition int position, @Nullable Bundle options);
}
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 27d3b81..18dd53b 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
@@ -19,11 +19,19 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
import android.content.Context;
+import android.content.pm.LauncherApps;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -69,7 +77,7 @@
}
@Override
- public boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition) {
+ public boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
@@ -79,7 +87,7 @@
@Override
public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @SideStagePosition int sideStagePosition) {
+ @StagePosition int sideStagePosition) {
return mStageCoordinator.moveToSideStage(task, sideStagePosition);
}
@@ -89,7 +97,7 @@
}
@Override
- public void setSideStagePosition(@SideStagePosition int sideStagePosition) {
+ public void setSideStagePosition(@StagePosition int sideStagePosition) {
mStageCoordinator.setSideStagePosition(sideStagePosition);
}
@@ -109,8 +117,103 @@
}
@Override
- public void updateActivityOptions(Bundle opts, @SideStagePosition int position) {
- mStageCoordinator.updateActivityOptions(opts, position);
+ public void registerSplitScreenListener(SplitScreenListener listener) {
+ mStageCoordinator.registerSplitScreenListener(listener);
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(SplitScreenListener listener) {
+ mStageCoordinator.unregisterSplitScreenListener(listener);
+ }
+
+ @Override
+ public void startTask(int taskId,
+ @StageType int stage, @StagePosition int position, @Nullable Bundle options) {
+ options = resolveStartStage(stage, position, options);
+
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ }
+
+ @Override
+ public void startShortcut(String packageName, String shortcutId, @StageType int stage,
+ @StagePosition int position, @Nullable Bundle options, UserHandle user) {
+ options = resolveStartStage(stage, position, options);
+
+ try {
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ options, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
+ @Override
+ public void startIntent(PendingIntent intent,
+ @StageType int stage, @StagePosition int position, @Nullable Bundle options) {
+ options = resolveStartStage(stage, position, options);
+
+ try {
+ intent.send(null, 0, null, null, null, null, options);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Failed to launch activity", e);
+ }
+ }
+
+ private Bundle resolveStartStage(@StageType int stage, @StagePosition int position,
+ @Nullable Bundle options) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: {
+ // Use the stage of the specified position is valid.
+ if (position != STAGE_POSITION_UNDEFINED) {
+ if (position == mStageCoordinator.getSideStagePosition()) {
+ options = resolveStartStage(STAGE_TYPE_SIDE, position, options);
+ } else {
+ options = resolveStartStage(STAGE_TYPE_MAIN, position, options);
+ }
+ } else {
+ // Exit split-screen and launch fullscreen since stage wasn't specified.
+ mStageCoordinator.exitSplitScreen();
+ }
+ break;
+ }
+ case STAGE_TYPE_SIDE: {
+ if (position != STAGE_POSITION_UNDEFINED) {
+ mStageCoordinator.setSideStagePosition(position);
+ } else {
+ position = mStageCoordinator.getSideStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ mStageCoordinator.updateActivityOptions(options, position);
+ break;
+ }
+ case STAGE_TYPE_MAIN: {
+ if (position != STAGE_POSITION_UNDEFINED) {
+ // Set the side stage opposite of what we want to the main stage.
+ final int sideStagePosition = position == STAGE_POSITION_TOP_OR_LEFT
+ ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT;
+ mStageCoordinator.setSideStagePosition(sideStagePosition);
+ } else {
+ position = mStageCoordinator.getMainStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ mStageCoordinator.updateActivityOptions(options, position);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown stage=" + stage);
+ }
+
+ return options;
}
@Override
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 d571e75..176852b 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
@@ -17,12 +17,14 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.app.ActivityManager;
import android.content.Context;
@@ -41,6 +43,8 @@
import com.android.wm.shell.common.split.SplitLayout;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -64,8 +68,7 @@
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
- private @SplitScreen.SideStagePosition int mSideStagePosition =
- SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+ private @SplitScreen.StagePosition int mSideStagePosition = STAGE_POSITION_BOTTOM_OR_RIGHT;
private final int mDisplayId;
private SplitLayout mSplitLayout;
@@ -75,6 +78,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private DisplayAreaInfo mDisplayAreaInfo;
private final Context mContext;
+ private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) {
@@ -107,7 +111,7 @@
}
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @SplitScreen.SideStagePosition int sideStagePosition) {
+ @SplitScreen.StagePosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStagePosition = sideStagePosition;
mMainStage.activate(getMainStageBounds(), wct);
@@ -130,7 +134,16 @@
return result;
}
- void setSideStagePosition(@SplitScreen.SideStagePosition int sideStagePosition) {
+ @SplitScreen.StagePosition int getSideStagePosition() {
+ return mSideStagePosition;
+ }
+
+ @SplitScreen.StagePosition int getMainStagePosition() {
+ return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT;
+ }
+
+ void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
mSideStagePosition = sideStagePosition;
if (mSideStageListener.mVisible) {
onStageVisibilityChanged(mSideStageListener);
@@ -163,7 +176,7 @@
outBottomOrRightBounds.set(mSplitLayout.getBounds2());
}
- void updateActivityOptions(Bundle opts, @SplitScreen.SideStagePosition int position) {
+ void updateActivityOptions(Bundle opts, @SplitScreen.StagePosition int position) {
final StageTaskListener stage = position == mSideStagePosition ? mSideStage : mMainStage;
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
@@ -176,6 +189,35 @@
}
}
+ void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
+
+ void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void onStageChildTaskStatusChanged(
+ StageListenerImpl stageListener, int taskId, boolean present) {
+
+ int stage;
+ if (present) {
+ stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ } else {
+ // No longer on any stage
+ stage = STAGE_TYPE_UNDEFINED;
+ }
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onTaskStageChanged(taskId, stage);
+ }
+ }
+
private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -299,7 +341,7 @@
@Override
public void onSnappedToDismiss(boolean bottomOrRight) {
final boolean mainStageToTop = bottomOrRight
- && mSideStagePosition == SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+ && mSideStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
exitSplitScreen(mainStageToTop ? mMainStage : mSideStage);
}
@@ -326,8 +368,8 @@
@Override
public void onDoubleTappedDivider() {
- setSideStagePosition(mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
- ? SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT : SIDE_STAGE_POSITION_TOP_OR_LEFT);
+ setSideStagePosition(mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT);
}
@Override
@@ -380,12 +422,12 @@
}
private Rect getSideStageBounds() {
- return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
+ return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
}
private Rect getMainStageBounds() {
- return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
+ return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
}
@@ -429,6 +471,11 @@
}
@Override
+ public void onChildTaskStatusChanged(int taskId, boolean present) {
+ StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present);
+ }
+
+ @Override
public void onRootTaskVanished() {
reset();
StageCoordinator.this.onStageRootTaskVanished(this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 1aa7552..6532993 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -58,6 +58,7 @@
public interface StageListenerCallbacks {
void onRootTaskAppeared();
void onStatusChanged(boolean visible, boolean hasChildren);
+ void onChildTaskStatusChanged(int taskId, boolean present);
void onRootTaskVanished();
}
private final StageListenerCallbacks mCallbacks;
@@ -83,9 +84,11 @@
mRootTaskInfo = taskInfo;
mCallbacks.onRootTaskAppeared();
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
- mChildrenLeashes.put(taskInfo.taskId, leash);
- mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ final int taskId = taskInfo.taskId;
+ mChildrenLeashes.put(taskId, leash);
+ mChildrenTaskInfo.put(taskId, taskInfo);
updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -120,6 +123,7 @@
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
sendStatusChanged();
+ mCallbacks.onChildTaskStatusChanged(taskId, false /* present */);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -134,6 +138,13 @@
wct.reorder(mRootTaskInfo.token, visible /* onTop */);
}
+ void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
+ @SplitScreen.StageType int stage) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ listener.onTaskStageChanged(mChildrenTaskInfo.keyAt(i), stage);
+ }
+ }
+
private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash, boolean firstAppeared) {
final Point taskPositionInParent = taskInfo.positionInParent;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 3198725..b5e1896 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -220,9 +220,8 @@
final InputChannel tmpInputChannel = new InputChannel();
mainExecutor.execute(() -> {
try {
- final int res = session.addToDisplay(window, layoutParams, View.GONE,
- displayId, mTmpInsetsState, tmpFrames.frame, tmpInputChannel,
- mTmpInsetsState, mTempControls);
+ final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
+ mTmpInsetsState, tmpInputChannel, mTmpInsetsState, mTempControls);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
return;
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 4a498d2..a57ac35 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -18,7 +18,7 @@
name: "WMShellFlickerTests",
srcs: ["src/**/*.java", "src/**/*.kt"],
manifest: "AndroidManifest.xml",
- test_config: "AndroidTestPhysicalDevices.xml",
+ test_config: "AndroidTest.xml",
platform_apis: true,
certificate: "platform",
test_suites: ["device-tests"],
@@ -36,25 +36,3 @@
"wmshell-flicker-test-components",
],
}
-
-android_test {
- name: "WMShellFlickerTestsVirtual",
- srcs: ["src/**/*.java", "src/**/*.kt"],
- manifest: "AndroidManifest.xml",
- test_config: "AndroidTestVirtualDevices.xml",
- platform_apis: true,
- certificate: "platform",
- libs: ["android.test.runner"],
- static_libs: [
- "androidx.test.ext.junit",
- "flickerlib",
- "truth-prebuilt",
- "app-helpers-core",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
- "wm-flicker-common-assertions",
- "wm-flicker-common-app-helpers",
- "platform-test-annotations",
- "wmshell-flicker-test-components",
- ],
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
rename to libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
deleted file mode 100644
index 0738608..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2020 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests">
- <option name="test-tag" value="FlickerTests" />
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <!-- keeps the screen on during tests -->
- <option name="screen-always-on" value="on" />
- <!-- prevents the phone from restarting -->
- <option name="force-skip-system-props" value="true" />
- <!-- set WM tracing verbose level to all -->
- <option name="run-command" value="cmd window tracing level all" />
- <!-- inform WM to log all transactions -->
- <option name="run-command" value="cmd window tracing transaction" />
- <!-- restart launcher to activate TAPL -->
- <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner">
- <!-- reboot the device to teardown any crashed tests -->
- <option name="cleanup-action" value="REBOOT" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="WMShellFlickerTests.apk"/>
- <option name="test-file-name" value="WMShellFlickerTestApp.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.wm.shell.flicker"/>
- <option name="exclude-annotation" value="androidx.test.filters.RequiresDevice" />
- <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
- <option name="shell-timeout" value="6600s" />
- <option name="test-timeout" value="6000s" />
- <option name="hidden-api-checks" value="false" />
- </test>
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/sdcard/flicker" />
- <option name="collect-on-run-ended-only" value="true" />
- <option name="clean-up" value="true" />
- </metrics_collector>
-</configuration>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index ccfdce6..7ad7553 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,33 +18,34 @@
import android.graphics.Region
import android.view.Surface
-import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
+import com.android.server.wm.flicker.dsl.LayersAssertionBuilderLegacy
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.DOCKED_STACK_DIVIDER
@JvmOverloads
-fun LayersAssertionBuilder.appPairsDividerIsVisible(
+fun LayersAssertionBuilderLegacy.appPairsDividerIsVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsVisible", bugId, enabled) {
- this.isVisible(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ this.isVisible(APP_PAIR_SPLIT_DIVIDER)
}
}
@JvmOverloads
-fun LayersAssertionBuilder.appPairsDividerIsInvisible(
+fun LayersAssertionBuilderLegacy.appPairsDividerIsInvisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsInVisible", bugId, enabled) {
- this.notExists(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ this.notExists(APP_PAIR_SPLIT_DIVIDER)
}
}
@JvmOverloads
-fun LayersAssertionBuilder.appPairsDividerBecomesVisible(
+fun LayersAssertionBuilderLegacy.appPairsDividerBecomesVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -56,7 +57,7 @@
}
@JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerIsVisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerIsVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -66,7 +67,7 @@
}
@JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerBecomesVisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerBecomesVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -78,7 +79,7 @@
}
@JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerBecomesInvisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -90,7 +91,7 @@
}
@JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerIsInvisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerIsInvisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -100,33 +101,33 @@
}
@JvmOverloads
-fun LayersAssertionBuilder.appPairsPrimaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.appPairsPrimaryBoundsIsVisible(
rotation: Int,
primaryLayerName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
end("PrimaryAppBounds", bugId, enabled) {
- val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation))
}
}
@JvmOverloads
-fun LayersAssertionBuilder.appPairsSecondaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.appPairsSecondaryBoundsIsVisible(
rotation: Int,
secondaryLayerName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
end("SecondaryAppBounds", bugId, enabled) {
- val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation))
}
}
@JvmOverloads
-fun LayersAssertionBuilder.dockedStackPrimaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.dockedStackPrimaryBoundsIsVisible(
rotation: Int,
primaryLayerName: String,
bugId: Int = 0,
@@ -139,7 +140,7 @@
}
@JvmOverloads
-fun LayersAssertionBuilder.dockedStackSecondaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.dockedStackSecondaryBoundsIsVisible(
rotation: Int,
secondaryLayerName: String,
bugId: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 3953c1c..89bbdb0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -135,12 +135,4 @@
throw RuntimeException(e)
}
}
-
- companion object {
- const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
- const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
- const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
- const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
- const val IMAGE_WALLPAPER = "ImageWallpaper"
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index 5cbfec6..257350b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -21,12 +21,12 @@
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
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.buildTestTag
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.APP_PAIR_SPLIT_DIVIDER
import com.android.wm.shell.flicker.appPairsDividerIsVisible
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index f57a000..0b001f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -21,12 +21,12 @@
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
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.buildTestTag
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.APP_PAIR_SPLIT_DIVIDER
import com.android.wm.shell.flicker.appPairsDividerIsInvisible
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index 7191e8e..aafa9bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -27,11 +27,9 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.appPairsDividerIsVisible
@@ -73,13 +71,14 @@
assertions {
layersTrace {
navBarLayerRotatesAndScales(Surface.ROTATION_0, configuration.endRotation,
- enabled = !configuration.startRotation.isRotated())
- statusBarLayerRotatesScales(Surface.ROTATION_0, configuration.endRotation)
- appPairsDividerIsVisible()
+ enabled = false)
+ statusBarLayerRotatesScales(Surface.ROTATION_0, configuration.endRotation,
+ enabled = false)
+ appPairsDividerIsVisible(enabled = false)
appPairsPrimaryBoundsIsVisible(configuration.endRotation,
- primaryApp.defaultWindowName, 172776659)
+ primaryApp.defaultWindowName, bugId = 172776659)
appPairsSecondaryBoundsIsVisible(configuration.endRotation,
- secondaryApp.defaultWindowName, 172776659)
+ secondaryApp.defaultWindowName, bugId = 172776659)
}
windowManagerTrace {
navBarWindowIsAlwaysVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
deleted file mode 100644
index dea5c30..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,208 +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.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickstep
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-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.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import org.junit.Assert
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test SplitScreen launch.
- * To run this test: `atest WMShellFlickerTests:EnterLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testLaunchActivity = "launch_splitScreen_test_activity"
- withTestName {
- testLaunchActivity
- }
- setup {
- eachRun {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- splitScreenApp.exit()
- secondaryApp.exit()
- nonResizeableApp.exit()
- }
- }
- assertions {
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- }
- }
-
- @Test
- fun testEnterSplitScreen_dockActivity() {
- val testTag = "testEnterSplitScreen_dockActivity"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- }
- assertions {
- layersTrace {
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackDividerBecomesVisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- LIVE_WALLPAPER_PACKAGE_NAME)
- )
- }
- windowManagerTrace {
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- fun testEnterSplitScreen_launchToSide() {
- val testTag = "testEnterSplitScreen_launchToSide"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- splitScreenApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackSecondaryBoundsIsVisible(
- rotation, secondaryApp.defaultWindowName, 169271943)
- dockedStackDividerBecomesVisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
- }
- windowManagerTrace {
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- .isVisible(secondaryApp.defaultWindowName)
- }
- visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName))
- }
- }
- }
- }
-
- @FlakyTest(bugId = 173875043)
- @Test
- fun testNonResizeableNotDocked() {
- val testTag = "testNonResizeableNotDocked"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- nonResizeableApp.launchViaIntent()
- uiDevice.openQuickstep()
- if (uiDevice.canSplitScreen()) {
- Assert.fail("Non-resizeable app should not enter split screen")
- }
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName)
- )
- }
- windowManagerTrace {
- end("appWindowIsVisible") {
- isInvisible(nonResizeableApp.defaultWindowName)
- }
- visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName))
- }
- }
- }
- }
-
- 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/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
new file mode 100644
index 0000000..5374bd9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity and dock to primary split screen
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenDockActivity(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testLegacySplitScreenDockActivity", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ }
+ assertions {
+ layersTrace {
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 169271943)
+ dockedStackDividerBecomesVisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME,
+ WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+ splitScreenApp.defaultWindowName),
+ bugId = 178531736
+ )
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME,
+ WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+ splitScreenApp.defaultWindowName),
+ bugId = 178531736
+ )
+ end("appWindowIsVisible") {
+ isVisible(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
new file mode 100644
index 0000000..d750403
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity to primary split screen and dock secondary activity to side
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenLaunchToSide(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testLegacySplitScreenLaunchToSide", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ secondaryApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 169271943)
+ dockedStackSecondaryBoundsIsVisible(
+ configuration.startRotation,
+ secondaryApp.defaultWindowName, bugId = 169271943)
+ dockedStackDividerBecomesVisible()
+ // TODO(b/178447631) Remove Splash Screen from white list when flicker lib
+ // add a wait for splash screen be gone
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName)
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
new file mode 100644
index 0000000..e361923
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.canSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickstep
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open non-resizable activity will auto exit split screen mode
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNonResizableNotDock`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 173875043)
+class EnterSplitScreenNonResizableNotDock(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testLegacySplitScreenNonResizeableActivityNotDock", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ device.openQuickstep()
+ if (device.canSplitScreen()) {
+ Assert.fail("Non-resizeable app should not enter split screen")
+ }
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME,
+ SPLASH_SCREEN_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(WALLPAPER_TITLE,
+ LAUNCHER_PACKAGE_NAME,
+ SPLASH_SCREEN_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+ )
+ end("appWindowIsVisible") {
+ isInvisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
new file mode 100644
index 0000000..7782364
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open resizeable activity split in primary, and drag divider to bottom exit split screen
+ * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitLegacySplitScreenFromBottom(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testExitLegacySplitScreenFromBottom", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ device.exitSplitScreenFromBottom()
+ }
+ assertions {
+ layersTrace {
+ layerBecomesInvisible(DOCKED_STACK_DIVIDER)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesInVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
deleted file mode 100644
index a3b8673..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
+++ /dev/null
@@ -1,95 +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.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-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.endRotation
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-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.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottomTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenFromBottomTest(
- testSpec: FlickerTestRunnerFactory.TestSpec
-) : FlickerTestRunner(testSpec) {
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
- // TODO(b/162923992) Use of multiple segments of flicker spec for testing
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) {
- configuration ->
- withTestName {
- buildTestTag("exitSplitScreenFromBottom", configuration)
- }
- repeat { configuration.repetitions }
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview()
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen()
- device.waitForIdle()
- this.setRotation(configuration.endRotation)
- }
- }
- teardown {
- eachRun {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
- }
- splitScreenApp.exit()
- }
- }
- transitions {
- device.exitSplitScreenFromBottom()
- }
- assertions {
- windowManagerTrace {
- all("isNotEmpty") { isNotEmpty() }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
deleted file mode 100644
index 701b0d0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,153 +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.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.util.Rational
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.resizeSplitScreen
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test exit SplitScreen mode.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testLaunchActivity = "launch_splitScreen_test_activity"
- withTestName {
- testLaunchActivity
- }
- setup {
- eachRun {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- }
- }
- teardown {
- eachRun {
- splitScreenApp.exit()
- secondaryApp.exit()
- }
- }
- assertions {
- windowManagerTrace {
- visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
- layersTrace {
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME))
- }
- }
- }
-
- @Test
- fun testEnterSplitScreen_exitPrimarySplitScreenMode() {
- val testTag = "testEnterSplitScreen_exitPrimarySplitScreenMode"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- uiDevice.exitSplitScreen()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- layerBecomesInvisible(splitScreenApp.defaultWindowName)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsInvisible") {
- isInvisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- @FlakyTest(bugId = 172811376)
- fun testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen() {
- val testTag = "testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.reopenAppFromOverview()
- uiDevice.resizeSplitScreen(startRatio)
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- private val startRatio = Rational(1, 3)
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
new file mode 100644
index 0000000..59f6aaf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+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.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen, and open secondary to side, exit primary split
+ * and test secondary activity become full screen.
+ * To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPrimarySplitScreenShowSecondaryFullscreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testExitPrimarySplitScreenShowSecondaryFullscreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ secondaryApp.reopenAppFromOverview()
+ // TODO(b/175687842) Can not find Split screen divider, use exit() instead
+ splitScreenApp.exit()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible(bugId = 175687842)
+ layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
similarity index 60%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 4dcbdff..03b6edf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -52,13 +52,13 @@
/**
* Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncherTest`
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher`
*/
@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LegacySplitScreenToLauncherTest(
+class LegacySplitScreenToLauncher(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
@@ -67,68 +67,68 @@
fun getParams(): Collection<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
+ .launcherStrategy.supportedLauncherPackage
val testApp = SimpleAppHelper(instrumentation)
// b/161435597 causes the test not to work on 90 degrees
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
- withTestName {
- buildTestTag("splitScreenToLauncher", configuration)
+ withTestName {
+ buildTestTag("splitScreenToLauncher", configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
}
- repeat { configuration.repetitions }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview()
- }
- eachRun {
- testApp.launchViaIntent(wmHelper)
- this.setRotation(configuration.endRotation)
- device.launchSplitScreen()
- device.waitForIdle()
- }
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ this.setRotation(configuration.endRotation)
+ device.launchSplitScreen()
+ device.waitForIdle()
}
- teardown {
- eachRun {
- testApp.exit()
- }
- test {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
- }
- }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
}
- transitions {
- device.exitSplitScreen()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
-
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(configuration.endRotation)
- navBarLayerRotatesAndScales(configuration.endRotation)
- statusBarLayerRotatesScales(configuration.endRotation)
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName))
-
- // b/161435597 causes the test not to work on 90 degrees
- dockedStackDividerBecomesInvisible()
-
- layerBecomesInvisible(testApp.getPackage())
- }
-
- eventLog {
- focusDoesNotChange(bugId = 151179149)
+ test {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
}
}
}
+ transitions {
+ device.exitSplitScreen()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation)
+ navBarLayerRotatesAndScales(configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.endRotation)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(launcherPackageName))
+
+ // b/161435597 causes the test not to work on 90 degrees
+ dockedStackDividerBecomesInvisible()
+
+ layerBecomesInvisible(testApp.getPackage())
+ }
+
+ eventLog {
+ focusDoesNotChange(bugId = 151179149)
+ }
+ }
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
new file mode 100644
index 0000000..328ff88
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -0,0 +1,99 @@
+/*
+ * 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/LICENSE2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.legacysplitscreen
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.view.Surface
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.startRotation
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class LegacySplitScreenTransition(
+ protected val instrumentation: Instrumentation
+) {
+ internal val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
+ internal val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+ internal val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
+ internal val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
+ .launcherStrategy.supportedLauncherPackage
+ internal val LIVE_WALLPAPER_PACKAGE_NAME =
+ "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+ internal val LETTERBOX_NAME = "Letterbox"
+ internal val TOAST_NAME = "Toast"
+ internal val SPLASH_SCREEN_NAME = "Splash Screen"
+
+ internal open val defaultTransitionSetup: FlickerBuilder.(Bundle) -> Unit
+ get() = { configuration ->
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ secondaryApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ splitScreenApp.exit()
+ secondaryApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ }
+
+ internal open val cleanSetup: FlickerBuilder.(Bundle) -> Unit
+ get() = { configuration ->
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ nonResizeableApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ }
+
+ internal open val customRotateSetup: FlickerBuilder.(Bundle) -> Unit
+ get() = { configuration ->
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ secondaryApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ }
+ }
+ teardown {
+ eachRun {
+ splitScreenApp.exit()
+ secondaryApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
new file mode 100644
index 0000000..f2a7cda
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via recent overview)
+ * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableDismissInLegacySplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testNonResizableDismissInLegacySplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen()
+ nonResizeableApp.reopenAppFromOverview()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ assertions {
+ layersTrace {
+ layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, cleanSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
deleted file mode 100644
index 6fca580..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,92 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableDismissInLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
- @Test
- fun testNonResizableDismissInLegacySplitScreenTest() {
- val testTag = "testNonResizableDismissInLegacySplitScreenTest"
-
- runWithFlicker(transitionSetup) {
- withTestName { testTag }
- repeat { SplitScreenHelper.TEST_REPETITIONS }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen()
- nonResizeableApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
- TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME),
- bugId = 178447631
- )
- }
- windowManagerTrace {
- end("nonResizeableAppWindowIsVisible") {
- isVisible(nonResizeableApp.defaultWindowName)
- .isInvisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
- 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/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
new file mode 100644
index 0000000..421ecff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via intent)
+ * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableLaunchInLegacySplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testNonResizableLaunchInLegacySplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen()
+ nonResizeableApp.launchViaIntent(wmHelper)
+ wmHelper.waitForAppTransitionIdle()
+ }
+ assertions {
+ layersTrace {
+ layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER,
+ LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER,
+ LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, cleanSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
deleted file mode 100644
index deae41f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,92 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableLaunchInLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
- @Test
- fun testNonResizableLaunchInLegacySplitScreenTest() {
- val testTag = "testNonResizableLaunchInLegacySplitScreenTest"
-
- runWithFlicker(transitionSetup) {
- withTestName { testTag }
- repeat { SplitScreenHelper.TEST_REPETITIONS }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen()
- nonResizeableApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
- TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME),
- bugId = 178447631
- )
- }
- windowManagerTrace {
- end("nonResizeableAppWindowIsVisible") {
- isVisible(nonResizeableApp.defaultWindowName)
- .isInvisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
- 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/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
new file mode 100644
index 0000000..c802ffe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.appWindowBecomesVisible
+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.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppToLegacySplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val wmHelper = WindowManagerStateHelper()
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testOpenAppToLegacySplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ assertions {
+ windowManagerTrace {
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+ bugId = 178447631)
+ appWindowBecomesVisible(splitScreenApp.getPackage())
+ }
+
+ layersTrace {
+ noUncoveredRegions(configuration.startRotation, enabled = false)
+ statusBarLayerIsAlwaysVisible()
+ appPairsDividerBecomesVisible()
+ layerBecomesVisible(splitScreenApp.getPackage())
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+ bugId = 178447631)
+ }
+
+ eventLog {
+ focusChanges(splitScreenApp.`package`,
+ "recents_animation_input_consumer", "NexusLauncherActivity",
+ bugId = 151179149)
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
deleted file mode 100644
index 6200a69..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,109 +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.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppToLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- @Test
- fun OpenAppToLegacySplitScreenTest() {
- val testTag = "OpenAppToLegacySplitScreenTest"
- val helper = WindowManagerStateHelper()
- runWithFlicker(transitionSetup) {
- withTestName { testTag }
- repeat { SplitScreenHelper.TEST_REPETITIONS }
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.pressHome()
- this.setRotation(rotation)
- }
- }
- transitions {
- device.launchSplitScreen()
- helper.waitForAppTransitionIdle()
- }
- assertions {
- windowManagerTrace {
- visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- LETTER_BOX_NAME)
- )
- appWindowBecomesVisible(splitScreenApp.getPackage())
- }
-
- layersTrace {
- navBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation, enabled = false)
- statusBarLayerIsAlwaysVisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- LETTER_BOX_NAME))
- appPairsDividerBecomesVisible()
- layerBecomesVisible(splitScreenApp.getPackage())
- }
-
- eventLog {
- focusChanges(splitScreenApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity",
- bugId = 151179149)
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- // TODO(b/161435597) causes the test not to work on 90 degrees
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
similarity index 99%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 95c1c16..54a37d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -58,7 +58,7 @@
/**
* Test split screen resizing window transitions.
- * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreenTest`
+ * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen`
*
* Currently it runs only in 0 degrees because of b/156100803
*/
@@ -67,7 +67,7 @@
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 159096424)
-class ResizeLegacySplitScreenTest(
+class ResizeLegacySplitScreen(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..214269e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen and rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppAndEnterSplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateOneLaunchedAppAndEnterSplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ this.setRotation(configuration.startRotation)
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..4290c92
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppInSplitScreenMode(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateOneLaunchedAppInSplitScreenMode", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ this.setRotation(configuration.startRotation)
+ device.launchSplitScreen()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
deleted file mode 100644
index 07571c3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
+++ /dev/null
@@ -1,150 +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.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateOneLaunchedAppTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenRotationSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testSetupRotation = "testSetupRotation"
- withTestName {
- testSetupRotation
- }
- setup {
- test {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- setRotation(Surface.ROTATION_0)
- splitScreenApp.exit()
- secondaryApp.exit()
- }
- }
- }
-
- @Test
- fun testRotateInSplitScreenMode() {
- val testTag = "testEnterSplitScreen_launchToSide"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- setRotation(rotation)
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- fun testRotateAndEnterSplitScreenMode() {
- val testTag = "testRotateAndEnterSplitScreenMode"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- setRotation(rotation)
- uiDevice.launchSplitScreen()
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
- 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/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..4095b9a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppAndEnterSplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateTwoLaunchedAppAndEnterSplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ this.setRotation(configuration.startRotation)
+ device.launchSplitScreen()
+ secondaryApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, 175687842)
+ dockedStackSecondaryBoundsIsVisible(
+ configuration.startRotation,
+ secondaryApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..aebf606
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+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.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppInSplitScreenMode(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateTwoLaunchedAppInSplitScreenMode", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ setup {
+ eachRun {
+ device.launchSplitScreen()
+ splitScreenApp.reopenAppFromOverview()
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ transitions {
+ this.setRotation(configuration.startRotation)
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 175687842)
+ dockedStackSecondaryBoundsIsVisible(
+ configuration.startRotation,
+ secondaryApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
deleted file mode 100644
index d8014d3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
+++ /dev/null
@@ -1,163 +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.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateTwoLaunchedAppTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenRotationSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testSetupRotation = "testSetupRotation"
- withTestName {
- testSetupRotation
- }
- setup {
- test {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- setRotation(Surface.ROTATION_0)
- splitScreenApp.exit()
- secondaryApp.exit()
- }
- }
- }
-
- @Test
- fun testRotateInSplitScreenMode() {
- val testTag = "testRotateInSplitScreenMode"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- splitScreenApp.reopenAppFromOverview()
- setRotation(rotation)
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackSecondaryBoundsIsVisible(
- rotation, secondaryApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- .isVisible(secondaryApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @FlakyTest(bugId = 173875043)
- @Test
- fun testRotateAndEnterSplitScreenMode() {
- val testTag = "testRotateAndEnterSplitScreenMode"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- setRotation(rotation)
- uiDevice.launchSplitScreen()
- splitScreenApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackSecondaryBoundsIsVisible(
- rotation, secondaryApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- .isVisible(secondaryApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
- 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/legacysplitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
deleted file mode 100644
index 01db4ed6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
+++ /dev/null
@@ -1,74 +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.legacysplitscreen
-
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-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.NonRotationTestBase
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-
-abstract class SplitScreenTestBase(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
- protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
- protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
- protected val LIVE_WALLPAPER_PACKAGE_NAME =
- "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
- protected val LETTER_BOX_NAME = "Letterbox"
- protected val TOAST_NAME = "Toast"
-
- protected val transitionSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- setup {
- eachRun {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- splitScreenApp.exit()
- nonResizeableApp.exit()
- }
- }
- assertions {
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- }
- }
-}
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 862776e..80ea9b9 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
@@ -16,11 +16,14 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
@@ -48,6 +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 org.junit.Before;
import org.junit.Test;
@@ -59,6 +63,9 @@
/**
* Tests for the shell task organizer.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ShellTaskOrganizerTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -68,6 +75,8 @@
private ITaskOrganizerController mTaskOrganizerController;
@Mock
private Context mContext;
+ @Mock
+ private SizeCompatUI mSizeCompatUI;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -102,7 +111,8 @@
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
- mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext));
+ mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
+ mSizeCompatUI));
}
@Test
@@ -257,6 +267,39 @@
assertTrue(mwListener.appeared.contains(task2));
}
+ @Test
+ public void testOnSizeCompatActivityChanged() {
+ final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+ taskInfo1.displayId = DEFAULT_DISPLAY;
+ taskInfo1.topActivityToken = mock(IBinder.class);
+ taskInfo1.topActivityInSizeCompat = false;
+ final TrackingTaskListener taskListener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, null);
+
+ // sizeCompatActivity is null if top activity is not in size compat.
+ verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+ taskInfo1.configuration.windowConfiguration.getBounds(),
+ null /* sizeCompatActivity*/ , taskListener);
+
+ // sizeCompatActivity is non-null if top activity is in size compat.
+ final RunningTaskInfo taskInfo2 =
+ createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+ taskInfo2.displayId = taskInfo1.displayId;
+ taskInfo2.topActivityToken = taskInfo1.topActivityToken;
+ taskInfo2.topActivityInSizeCompat = true;
+ mOrganizer.onTaskInfoChanged(taskInfo2);
+ verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+ taskInfo1.configuration.windowConfiguration.getBounds(),
+ taskInfo1.topActivityToken,
+ taskListener);
+
+ mOrganizer.onTaskVanished(taskInfo1);
+ verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+ null /* taskConfig */, null /* sizeCompatActivity*/,
+ null /* taskListener */);
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
index 495be41..21bc32c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
@@ -27,7 +27,6 @@
import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
@@ -234,14 +233,6 @@
verify(mOtherCallback).onActivityRotation(eq(123));
}
- @Test
- public void testOnSizeCompatModeActivityChanged() {
- IBinder b = mock(IBinder.class);
- mImpl.onSizeCompatModeActivityChanged(123, b);
- verify(mCallback).onSizeCompatModeActivityChanged(eq(123), eq(b));
- verify(mOtherCallback).onSizeCompatModeActivityChanged(eq(123), eq(b));
- }
-
/**
* Handler that synchronously calls TaskStackListenerImpl#handleMessage() when it receives a
* message.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 79bdaf4..2572106 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -29,6 +29,10 @@
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -86,11 +90,9 @@
@Mock
private ActivityTaskManager mActivityTaskManager;
+ // Both the split-screen and start interface.
@Mock
- private SplitScreen mSplitScreen;
-
- @Mock
- private DragAndDropPolicy.Starter mStarter;
+ private SplitScreen mSplitScreenStarter;
private DisplayLayout mLandscapeDisplayLayout;
private DisplayLayout mPortraitDisplayLayout;
@@ -126,7 +128,7 @@
mInsets = Insets.of(0, 0, 0, 0);
mPolicy = new DragAndDropPolicy(
- mContext, mActivityTaskManager, mSplitScreen, mStarter);
+ mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter);
mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -191,7 +193,7 @@
}
private void setInSplitScreen(boolean inSplitscreen) {
- doReturn(inSplitscreen).when(mSplitScreen).isSplitScreenVisible();
+ doReturn(inSplitscreen).when(mSplitScreenStarter).isSplitScreenVisible();
}
@Test
@@ -202,7 +204,8 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
}
@Test
@@ -213,12 +216,13 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mStarter).exitSplitScreen();
- verify(mStarter).startIntent(any(), any());
- reset(mStarter);
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+ reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test
@@ -229,12 +233,13 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mStarter).exitSplitScreen();
- verify(mStarter).startIntent(any(), any());
- reset(mStarter);
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+ reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test
@@ -245,7 +250,8 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
}
@Test
@@ -256,7 +262,8 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
}
@Test
@@ -268,12 +275,14 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
- reset(mStarter);
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+ reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test
@@ -285,12 +294,14 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
- reset(mStarter);
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+ reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mStarter).startIntent(any(), any());
+ verify(mSplitScreenStarter).startClipDescription(any(), any(),
+ eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index c565a4c..0087d91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import android.app.TaskInfo;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -54,6 +55,9 @@
private SurfaceControl mLeash;
@Mock
+ private TaskInfo mTaskInfo;
+
+ @Mock
private PipAnimationController.PipAnimationCallback mPipAnimationCallback;
@Before
@@ -70,7 +74,7 @@
@Test
public void getAnimator_withAlpha_returnFloatAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), 0f, 1f);
+ .getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f);
assertEquals("Expect ANIM_TYPE_ALPHA animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_ALPHA);
@@ -79,7 +83,7 @@
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), new Rect(), new Rect(), null,
+ .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null,
TRANSITION_DIRECTION_TO_PIP, 0);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
@@ -93,13 +97,13 @@
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
- .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
TRANSITION_DIRECTION_TO_PIP, 0);
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
- .getAnimator(mLeash, baseValue, startValue, endValue2, null,
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
TRANSITION_DIRECTION_TO_PIP, 0);
assertEquals("getAnimator with same type returns same animator",
@@ -111,13 +115,13 @@
@Test
public void getAnimator_setTransitionDirection() {
PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), 0f, 1f)
+ .getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
assertEquals("Transition to PiP mode",
animator.getTransitionDirection(), TRANSITION_DIRECTION_TO_PIP);
animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), 0f, 1f)
+ .getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f)
.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP);
assertEquals("Transition to fullscreen mode",
animator.getTransitionDirection(), TRANSITION_DIRECTION_LEAVE_PIP);
@@ -131,7 +135,7 @@
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
TRANSITION_DIRECTION_TO_PIP, 0);
animator.updateEndValue(endValue2);
@@ -145,7 +149,7 @@
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, baseValue, startValue, endValue, null,
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
TRANSITION_DIRECTION_TO_PIP, 0);
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
@@ -153,16 +157,16 @@
// onAnimationStart triggers onPipAnimationStart
animator.onAnimationStart(animator);
- verify(mPipAnimationCallback).onPipAnimationStart(animator);
+ verify(mPipAnimationCallback).onPipAnimationStart(mTaskInfo, animator);
// onAnimationCancel triggers onPipAnimationCancel
animator.onAnimationCancel(animator);
- verify(mPipAnimationCallback).onPipAnimationCancel(animator);
+ verify(mPipAnimationCallback).onPipAnimationCancel(mTaskInfo, animator);
// onAnimationEnd triggers onPipAnimationEnd
animator.onAnimationEnd(animator);
- verify(mPipAnimationCallback).onPipAnimationEnd(any(SurfaceControl.Transaction.class),
- eq(animator));
+ verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo),
+ any(SurfaceControl.Transaction.class), eq(animator));
}
/**
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 7a810a1..9430af9 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
@@ -18,27 +18,23 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Rational;
import android.util.Size;
-import android.view.Display;
import android.view.DisplayInfo;
import android.window.WindowContainerToken;
@@ -47,9 +43,8 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.pip.phone.PhonePipMenuController;
import org.junit.Before;
import org.junit.Test;
@@ -69,14 +64,17 @@
private PipTaskOrganizer mSpiedPipTaskOrganizer;
@Mock private DisplayController mMockdDisplayController;
- @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+
@Mock private PhonePipMenuController mMockPhonePipMenuController;
+ @Mock private PipAnimationController mMockPipAnimationController;
+ @Mock private PipTransitionController mMockPipTransitionController;
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
private TestShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState;
+ private PipBoundsAlgorithm mPipBoundsAlgorithm;
private ComponentName mComponent1;
private ComponentName mComponent2;
@@ -87,10 +85,12 @@
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
mPipBoundsState = new PipBoundsState(mContext);
+ mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
mMainExecutor = new TestShellExecutor();
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
- mMockPipBoundsAlgorithm, mMockPhonePipMenuController,
- mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
+ mPipBoundsAlgorithm, mMockPhonePipMenuController,
+ mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
+ mMockPipTransitionController, mMockOptionalSplitScreen, mMockdDisplayController,
mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
mMainExecutor.flushAll();
preparePipTaskOrg();
@@ -117,7 +117,7 @@
@Test
public void startSwipePipToHome_updatesLastPipComponentName() {
- mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, null, null);
+ mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, null, createPipParams(null));
assertEquals(mComponent1, mPipBoundsState.getLastPipComponentName());
}
@@ -126,7 +126,8 @@
public void startSwipePipToHome_updatesOverrideMinSize() {
final Size minSize = new Size(100, 80);
- mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize), null);
+ mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize),
+ createPipParams(null));
assertEquals(minSize, mPipBoundsState.getOverrideMinSize());
}
@@ -200,9 +201,6 @@
final DisplayInfo info = new DisplayInfo();
mPipBoundsState.setDisplayLayout(new DisplayLayout(info,
mContext.getResources(), true, true));
- when(mMockPipBoundsAlgorithm.getEntryDestinationBounds()).thenReturn(new Rect());
- when(mMockPipBoundsAlgorithm.getAdjustedDestinationBounds(any(), anyFloat()))
- .thenReturn(new Rect());
mSpiedPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
doNothing().when(mSpiedPipTaskOrganizer).enterPipWithAlphaAnimation(any(), anyLong());
doNothing().when(mSpiedPipTaskOrganizer).scheduleAnimateResizePip(any(), anyInt(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 62ffac4..cfe8463 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -46,6 +46,7 @@
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import org.junit.Before;
import org.junit.Test;
@@ -68,6 +69,7 @@
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
+ @Mock private PipTransitionController mMockPipTransitionController;
@Mock private PipTouchHandler mMockPipTouchHandler;
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
@@ -80,8 +82,8 @@
mPipController = new PipController(mContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockExecutor);
+ mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockExecutor);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -90,7 +92,7 @@
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTaskOrganizer).registerPipTransitionCallback(any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any());
}
@Test
@@ -113,8 +115,8 @@
assertNull(PipController.create(spyContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockExecutor));
+ mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockExecutor));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index b4cfbc2..449ad88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -38,6 +38,7 @@
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import org.junit.Before;
@@ -67,6 +68,9 @@
private PipTaskOrganizer mPipTaskOrganizer;
@Mock
+ private PipTransitionController mMockPipTransitionController;
+
+ @Mock
private FloatingContentCoordinator mFloatingContentCoordinator;
@Mock
@@ -98,7 +102,8 @@
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
- mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+ mMockPipTransitionController, mFloatingContentCoordinator, mPipUiEventLogger,
+ mMainExecutor);
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
mPipTouchHandler.setPipMotionHelper(mMotionHelper);
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
new file mode 100644
index 0000000..98f01ff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -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.sizecompatui;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatUIController}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SizeCompatUIControllerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatUIControllerTest extends ShellTestCase {
+ private static final int DISPLAY_ID = 0;
+
+ private final TestShellExecutor mShellMainExecutor = new TestShellExecutor();
+
+ private SizeCompatUIController mController;
+ private @Mock DisplayController mMockDisplayController;
+ private @Mock DisplayImeController mMockImeController;
+ private @Mock SizeCompatRestartButton mMockButton;
+ private @Mock IBinder mMockActivityToken;
+ private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(true).when(mMockButton).show();
+
+ mController = new SizeCompatUIController(mContext, mMockDisplayController,
+ mMockImeController, mShellMainExecutor) {
+ @Override
+ SizeCompatRestartButton createRestartButton(Context context, int displayId) {
+ return mMockButton;
+ }
+ };
+ }
+
+ @Test
+ public void testListenerRegistered() {
+ verify(mMockDisplayController).addDisplayWindowListener(mController);
+ verify(mMockImeController).addPositionProcessor(mController);
+ }
+
+ @Test
+ public void testOnSizeCompatInfoChanged() {
+ final int taskId = 12;
+ 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,
+ mMockActivityToken, mMockTaskListener);
+ mShellMainExecutor.flushAll();
+
+ verify(mMockButton).show();
+ 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);
+
+ mShellMainExecutor.flushAll();
+ verify(mMockButton).remove();
+ }
+
+ @Test
+ public void testChangeButtonVisibilityOnImeShowHide() {
+ final int taskId = 12;
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+ mMockActivityToken, mMockTaskListener);
+ mShellMainExecutor.flushAll();
+
+ // Verify that the restart button is hidden when IME is visible.
+ doReturn(View.VISIBLE).when(mMockButton).getVisibility();
+ mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+
+ verify(mMockButton).setVisibility(eq(View.GONE));
+
+ // Verify that the restart button is visible when IME is hidden.
+ doReturn(View.GONE).when(mMockButton).getVisibility();
+ mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+
+ verify(mMockButton).setVisibility(eq(View.VISIBLE));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 168e0df..d2d1812 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -19,7 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -71,7 +71,7 @@
public void testMoveToSideStage() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
- mStageCoordinator.moveToSideStage(task, SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
+ mStageCoordinator.moveToSideStage(task, STAGE_POSITION_BOTTOM_OR_RIGHT);
verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class));
verify(mSideStage).addTask(eq(task), any(Rect.class),
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index b54f7d8..4b4284a 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -41,6 +41,7 @@
"AssetDir.cpp",
"AssetManager.cpp",
"AssetManager2.cpp",
+ "AssetsProvider.cpp",
"AttributeResolution.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 011a0de..ca5981c0 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -16,533 +16,145 @@
#include "androidfw/ApkAssets.h"
-#include <algorithm>
-
#include "android-base/errors.h"
-#include "android-base/file.h"
#include "android-base/logging.h"
-#include "android-base/stringprintf.h"
-#include "android-base/unique_fd.h"
-#include "android-base/utf8.h"
-#include "utils/Compat.h"
-#include "ziparchive/zip_archive.h"
-
-#include "androidfw/Asset.h"
-#include "androidfw/Idmap.h"
-#include "androidfw/misc.h"
-#include "androidfw/Util.h"
namespace android {
using base::SystemErrorCodeToString;
using base::unique_fd;
-static const std::string kResourcesArsc("resources.arsc");
+constexpr const char* kResourcesArsc = "resources.arsc";
-ApkAssets::ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
- std::string path,
- time_t last_mod_time,
- package_property_t property_flags)
- : assets_provider_(std::move(assets_provider)),
- path_(std::move(path)),
- last_mod_time_(last_mod_time),
- property_flags_(property_flags) {
+ApkAssets::ApkAssets(std::unique_ptr<Asset> resources_asset,
+ std::unique_ptr<LoadedArsc> loaded_arsc,
+ std::unique_ptr<AssetsProvider> assets,
+ package_property_t property_flags,
+ std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<LoadedIdmap> loaded_idmap)
+ : resources_asset_(std::move(resources_asset)),
+ loaded_arsc_(std::move(loaded_arsc)),
+ assets_provider_(std::move(assets)),
+ property_flags_(property_flags),
+ idmap_asset_(std::move(idmap_asset)),
+ loaded_idmap_(std::move(loaded_idmap)) {}
+
+std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path, package_property_t flags) {
+ return Load(ZipAssetsProvider::Create(path), flags);
}
-// Provides asset files from a zip file.
-class ZipAssetsProvider : public AssetsProvider {
- public:
- ~ZipAssetsProvider() override = default;
-
- static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
- ::ZipArchiveHandle unmanaged_handle;
- const int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
- if (result != 0) {
- LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
- ::CloseArchive(unmanaged_handle);
- return {};
- }
-
- return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider(path, path, unmanaged_handle));
- }
-
- static std::unique_ptr<const AssetsProvider> Create(
- unique_fd fd, const std::string& friendly_name, const off64_t offset = 0,
- const off64_t length = ApkAssets::kUnknownLength) {
-
- ::ZipArchiveHandle unmanaged_handle;
- const int32_t result = (length == ApkAssets::kUnknownLength)
- ? ::OpenArchiveFd(fd.release(), friendly_name.c_str(), &unmanaged_handle)
- : ::OpenArchiveFdRange(fd.release(), friendly_name.c_str(), &unmanaged_handle, length,
- offset);
-
- if (result != 0) {
- LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
- << " and length " << length << ": " << ::ErrorCodeString(result);
- ::CloseArchive(unmanaged_handle);
- return {};
- }
-
- return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider({}, friendly_name,
- unmanaged_handle));
- }
-
- // Iterate over all files and directories within the zip. The order of iteration is not
- // guaranteed to be the same as the order of elements in the central directory but is stable for a
- // given zip file.
- bool ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f) const override {
- // If this is a resource loader from an .arsc, there will be no zip handle
- if (zip_handle_ == nullptr) {
- return false;
- }
-
- std::string root_path_full = root_path;
- if (root_path_full.back() != '/') {
- root_path_full += '/';
- }
-
- void* cookie;
- if (::StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
- return false;
- }
-
- std::string name;
- ::ZipEntry entry{};
-
- // We need to hold back directories because many paths will contain them and we want to only
- // surface one.
- std::set<std::string> dirs{};
-
- int32_t result;
- while ((result = ::Next(cookie, &entry, &name)) == 0) {
- StringPiece full_file_path(name);
- StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
-
- if (!leaf_file_path.empty()) {
- auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
- if (iter != leaf_file_path.end()) {
- std::string dir =
- leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
- dirs.insert(std::move(dir));
- } else {
- f(leaf_file_path, kFileTypeRegular);
- }
- }
- }
- ::EndIteration(cookie);
-
- // Now present the unique directories.
- for (const std::string& dir : dirs) {
- f(dir, kFileTypeDirectory);
- }
-
- // -1 is end of iteration, anything else is an error.
- return result == -1;
- }
-
- protected:
- std::unique_ptr<Asset> OpenInternal(
- const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
- if (file_exists) {
- *file_exists = false;
- }
-
- ::ZipEntry entry;
- int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
- if (result != 0) {
- return {};
- }
-
- if (file_exists) {
- *file_exists = true;
- }
-
- const int fd = ::GetFileDescriptor(zip_handle_.get());
- const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get());
- incfs::IncFsFileMap asset_map;
- if (entry.method == kCompressDeflated) {
- if (!asset_map.Create(fd, entry.offset + fd_offset, entry.compressed_length, GetPath())) {
- LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
- return {};
- }
-
- std::unique_ptr<Asset> asset =
- Asset::createFromCompressedMap(std::move(asset_map), entry.uncompressed_length, mode);
- if (asset == nullptr) {
- LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << friendly_name_ << "'";
- return {};
- }
- return asset;
- }
-
- if (!asset_map.Create(fd, entry.offset + fd_offset, entry.uncompressed_length, GetPath())) {
- LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
- return {};
- }
-
- unique_fd ufd;
- if (!GetPath()) {
- // If the `path` is not set, create a new `fd` for the new Asset to own in order to create
- // new file descriptors using Asset::openFileDescriptor. If the path is set, it will be used
- // to create new file descriptors.
- ufd = unique_fd(dup(fd));
- if (!ufd.ok()) {
- LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << friendly_name_ << "'";
- return {};
- }
- }
-
- auto asset = Asset::createFromUncompressedMap(std::move(asset_map), mode, std::move(ufd));
- if (asset == nullptr) {
- LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
- return {};
- }
- return asset;
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ZipAssetsProvider);
-
- explicit ZipAssetsProvider(std::string path,
- std::string friendly_name,
- ZipArchiveHandle unmanaged_handle)
- : zip_handle_(unmanaged_handle, ::CloseArchive),
- path_(std::move(path)),
- friendly_name_(std::move(friendly_name)) { }
-
- const char* GetPath() const {
- return path_.empty() ? nullptr : path_.c_str();
- }
-
- using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
- ZipArchivePtr zip_handle_;
- std::string path_;
- std::string friendly_name_;
-};
-
-class DirectoryAssetsProvider : AssetsProvider {
- public:
- ~DirectoryAssetsProvider() override = default;
-
- static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
- struct stat sb{};
- const int result = stat(path.c_str(), &sb);
- if (result == -1) {
- LOG(ERROR) << "Failed to find directory '" << path << "'.";
- return nullptr;
- }
-
- if (!S_ISDIR(sb.st_mode)) {
- LOG(ERROR) << "Path '" << path << "' is not a directory.";
- return nullptr;
- }
-
- return std::unique_ptr<AssetsProvider>(new DirectoryAssetsProvider(path));
- }
-
- protected:
- std::unique_ptr<Asset> OpenInternal(
- const std::string& path, Asset::AccessMode /* mode */, bool* file_exists) const override {
- const std::string resolved_path = ResolvePath(path);
- if (file_exists) {
- struct stat sb{};
- const int result = stat(resolved_path.c_str(), &sb);
- *file_exists = result != -1 && S_ISREG(sb.st_mode);
- }
-
- return ApkAssets::CreateAssetFromFile(resolved_path);
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(DirectoryAssetsProvider);
-
- explicit DirectoryAssetsProvider(std::string path) : path_(std::move(path)) { }
-
- inline std::string ResolvePath(const std::string& path) const {
- return base::StringPrintf("%s%c%s", path_.c_str(), OS_PATH_SEPARATOR, path.c_str());
- }
-
- const std::string path_;
-};
-
-// AssetProvider implementation that does not provide any assets. Used for ApkAssets::LoadEmpty.
-class EmptyAssetsProvider : public AssetsProvider {
- public:
- EmptyAssetsProvider() = default;
- ~EmptyAssetsProvider() override = default;
-
- protected:
- std::unique_ptr<Asset> OpenInternal(const std::string& /*path */,
- Asset::AccessMode /* mode */,
- bool* file_exists) const override {
- if (file_exists) {
- *file_exists = false;
- }
- return nullptr;
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider);
-};
-
-// AssetProvider implementation
-class MultiAssetsProvider : public AssetsProvider {
- public:
- ~MultiAssetsProvider() override = default;
-
- static std::unique_ptr<const AssetsProvider> Create(
- std::unique_ptr<const AssetsProvider> child, std::unique_ptr<const AssetsProvider> parent) {
- CHECK(parent != nullptr) << "parent provider must not be null";
- return (!child) ? std::move(parent)
- : std::unique_ptr<const AssetsProvider>(new MultiAssetsProvider(
- std::move(child), std::move(parent)));
- }
-
- bool ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f) const override {
- // TODO: Only call the function once for files defined in the parent and child
- return child_->ForEachFile(root_path, f) && parent_->ForEachFile(root_path, f);
- }
-
- protected:
- std::unique_ptr<Asset> OpenInternal(
- const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
- auto asset = child_->Open(path, mode, file_exists);
- return (asset) ? std::move(asset) : parent_->Open(path, mode, file_exists);
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MultiAssetsProvider);
-
- MultiAssetsProvider(std::unique_ptr<const AssetsProvider> child,
- std::unique_ptr<const AssetsProvider> parent)
- : child_(std::move(child)), parent_(std::move(parent)) { }
-
- std::unique_ptr<const AssetsProvider> child_;
- std::unique_ptr<const AssetsProvider> parent_;
-};
-
-// Opens the archive using the file path. Calling CloseArchive on the zip handle will close the
-// file.
-std::unique_ptr<const ApkAssets> ApkAssets::Load(
- const std::string& path, const package_property_t flags,
- std::unique_ptr<const AssetsProvider> override_asset) {
- auto assets = ZipAssetsProvider::Create(path);
- return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset))
- : nullptr;
+std::unique_ptr<ApkAssets> ApkAssets::LoadFromFd(base::unique_fd fd,
+ const std::string& debug_name,
+ package_property_t flags,
+ off64_t offset,
+ off64_t len) {
+ return Load(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags);
}
-// Opens the archive using the file file descriptor with the specified file offset and read length.
-// If the `assume_ownership` parameter is 'true' calling CloseArchive will close the file.
-std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(
- unique_fd fd, const std::string& friendly_name, const package_property_t flags,
- std::unique_ptr<const AssetsProvider> override_asset, const off64_t offset,
- const off64_t length) {
- CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
- CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
- << kUnknownLength;
-
- auto assets = ZipAssetsProvider::Create(std::move(fd), friendly_name, offset, length);
- return (assets) ? LoadImpl(std::move(assets), friendly_name, flags, std::move(override_asset))
- : nullptr;
+std::unique_ptr<ApkAssets> ApkAssets::Load(std::unique_ptr<AssetsProvider> assets,
+ package_property_t flags) {
+ return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */);
}
-std::unique_ptr<const ApkAssets> ApkAssets::LoadTable(
- const std::string& path, const package_property_t flags,
- std::unique_ptr<const AssetsProvider> override_asset) {
-
- auto assets = CreateAssetFromFile(path);
- return (assets) ? LoadTableImpl(std::move(assets), path, flags, std::move(override_asset))
- : nullptr;
+std::unique_ptr<ApkAssets> ApkAssets::LoadTable(std::unique_ptr<Asset> resources_asset,
+ std::unique_ptr<AssetsProvider> assets,
+ package_property_t flags) {
+ if (resources_asset == nullptr) {
+ return {};
+ }
+ return LoadImpl(std::move(resources_asset), std::move(assets), flags, nullptr /* idmap_asset */,
+ nullptr /* loaded_idmap */);
}
-std::unique_ptr<const ApkAssets> ApkAssets::LoadTableFromFd(
- unique_fd fd, const std::string& friendly_name, const package_property_t flags,
- std::unique_ptr<const AssetsProvider> override_asset, const off64_t offset,
- const off64_t length) {
-
- auto assets = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length);
- return (assets) ? LoadTableImpl(std::move(assets), friendly_name, flags,
- std::move(override_asset))
- : nullptr;
-}
-
-std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
- const package_property_t flags) {
+std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
+ package_property_t flags) {
CHECK((flags & PROPERTY_LOADER) == 0U) << "Cannot load RROs through loaders";
- std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path);
+ auto idmap_asset = AssetsProvider::CreateAssetFromFile(idmap_path);
if (idmap_asset == nullptr) {
+ LOG(ERROR) << "failed to read IDMAP " << idmap_path;
return {};
}
- const StringPiece idmap_data(
- reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
- static_cast<size_t>(idmap_asset->getLength()));
- std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data);
+ StringPiece idmap_data(reinterpret_cast<const char*>(idmap_asset->getBuffer(true /* aligned */)),
+ static_cast<size_t>(idmap_asset->getLength()));
+ auto loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data);
if (loaded_idmap == nullptr) {
LOG(ERROR) << "failed to load IDMAP " << idmap_path;
return {};
}
-
- auto overlay_path = std::string(loaded_idmap->OverlayApkPath());
- auto assets = ZipAssetsProvider::Create(overlay_path);
- return (assets) ? LoadImpl(std::move(assets), overlay_path, flags | PROPERTY_OVERLAY,
- nullptr /* override_asset */, std::move(idmap_asset),
- std::move(loaded_idmap))
- : nullptr;
-}
-std::unique_ptr<const ApkAssets> ApkAssets::LoadFromDir(
- const std::string& path, const package_property_t flags,
- std::unique_ptr<const AssetsProvider> override_asset) {
-
- auto assets = DirectoryAssetsProvider::Create(path);
- return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset))
- : nullptr;
-}
-
-std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(
- const package_property_t flags, std::unique_ptr<const AssetsProvider> override_asset) {
-
- auto assets = (override_asset) ? std::move(override_asset)
- : std::unique_ptr<const AssetsProvider>(new EmptyAssetsProvider());
- std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(std::move(assets), "empty" /* path */,
- -1 /* last_mod-time */, flags));
- loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
- // Need to force a move for mingw32.
- return std::move(loaded_apk);
-}
-
-std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
- unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
- if (!fd.ok()) {
- LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno);
+ const std::string overlay_path(loaded_idmap->OverlayApkPath());
+ auto overlay_assets = ZipAssetsProvider::Create(overlay_path);
+ if (overlay_assets == nullptr) {
return {};
}
- return CreateAssetFromFd(std::move(fd), path.c_str());
+ return LoadImpl(std::move(overlay_assets), flags | PROPERTY_OVERLAY, std::move(idmap_asset),
+ std::move(loaded_idmap));
}
-std::unique_ptr<Asset> ApkAssets::CreateAssetFromFd(base::unique_fd fd,
- const char* path,
- off64_t offset,
- off64_t length) {
- CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
- CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
- << kUnknownLength;
- if (length == kUnknownLength) {
- length = lseek64(fd, 0, SEEK_END);
- if (length < 0) {
- LOG(ERROR) << "Failed to get size of file '" << ((path) ? path : "anon") << "': "
- << SystemErrorCodeToString(errno);
- return {};
- }
- }
-
- incfs::IncFsFileMap file_map;
- if (!file_map.Create(fd, offset, static_cast<size_t>(length), path)) {
- LOG(ERROR) << "Failed to mmap file '" << ((path) ? path : "anon") << "': "
- << SystemErrorCodeToString(errno);
+std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
+ package_property_t property_flags,
+ std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<LoadedIdmap> loaded_idmap) {
+ if (assets == nullptr) {
return {};
}
- // If `path` is set, do not pass ownership of the `fd` to the new Asset since
- // Asset::openFileDescriptor can use `path` to create new file descriptors.
- return Asset::createFromUncompressedMap(std::move(file_map),
- Asset::AccessMode::ACCESS_RANDOM,
- (path) ? base::unique_fd(-1) : std::move(fd));
-}
-
-std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
- std::unique_ptr<const AssetsProvider> assets, const std::string& path,
- package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets,
- std::unique_ptr<Asset> idmap_asset, std::unique_ptr<const LoadedIdmap> idmap) {
-
- const time_t last_mod_time = getFileModDate(path.c_str());
-
// Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
bool resources_asset_exists = false;
- auto resources_asset_ = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
- &resources_asset_exists);
-
- assets = MultiAssetsProvider::Create(std::move(override_assets), std::move(assets));
-
- // Wrap the handle in a unique_ptr so it gets automatically closed.
- std::unique_ptr<ApkAssets>
- loaded_apk(new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
-
- if (!resources_asset_exists) {
- loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
- return std::move(loaded_apk);
- }
-
- loaded_apk->resources_asset_ = std::move(resources_asset_);
- if (!loaded_apk->resources_asset_) {
- LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
+ auto resources_asset = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
+ &resources_asset_exists);
+ if (resources_asset == nullptr && resources_asset_exists) {
+ LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << assets->GetDebugName()
+ << "'.";
return {};
}
- // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
- loaded_apk->idmap_asset_ = std::move(idmap_asset);
- loaded_apk->loaded_idmap_ = std::move(idmap);
-
- const auto data = loaded_apk->resources_asset_->getIncFsBuffer(true /* aligned */);
- const size_t length = loaded_apk->resources_asset_->getLength();
- if (!data || length == 0) {
- LOG(ERROR) << "Failed to read '" << kResourcesArsc << "' data in APK '" << path << "'.";
- return {};
- }
-
- loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, length, loaded_apk->loaded_idmap_.get(),
- property_flags);
- if (!loaded_apk->loaded_arsc_) {
- LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
- return {};
- }
-
- // Need to force a move for mingw32.
- return std::move(loaded_apk);
+ return LoadImpl(std::move(resources_asset), std::move(assets), property_flags,
+ std::move(idmap_asset), std::move(loaded_idmap));
}
-std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(
- std::unique_ptr<Asset> resources_asset, const std::string& path,
- package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets) {
-
- const time_t last_mod_time = getFileModDate(path.c_str());
-
- auto assets = (override_assets) ? std::move(override_assets)
- : std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider());
-
- std::unique_ptr<ApkAssets> loaded_apk(
- new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
- loaded_apk->resources_asset_ = std::move(resources_asset);
-
- const auto data = loaded_apk->resources_asset_->getIncFsBuffer(true /* aligned */);
- const size_t length = loaded_apk->resources_asset_->getLength();
- if (!data || length == 0) {
- LOG(ERROR) << "Failed to read resources table data in '" << path << "'.";
+std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
+ std::unique_ptr<AssetsProvider> assets,
+ package_property_t property_flags,
+ std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<LoadedIdmap> loaded_idmap) {
+ if (assets == nullptr ) {
return {};
}
- loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, length, nullptr /* loaded_idmap */,
- property_flags);
- if (loaded_apk->loaded_arsc_ == nullptr) {
- LOG(ERROR) << "Failed to read resources table in '" << path << "'.";
+ std::unique_ptr<LoadedArsc> loaded_arsc;
+ if (resources_asset != nullptr) {
+ const auto data = resources_asset->getIncFsBuffer(true /* aligned */);
+ const size_t length = resources_asset->getLength();
+ if (!data || length == 0) {
+ LOG(ERROR) << "Failed to read resources table in APK '" << assets->GetDebugName() << "'.";
+ return {};
+ }
+ loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
+ } else {
+ loaded_arsc = LoadedArsc::CreateEmpty();
+ }
+
+ if (loaded_arsc == nullptr) {
+ LOG(ERROR) << "Failed to load resources table in APK '" << assets->GetDebugName() << "'.";
return {};
}
- // Need to force a move for mingw32.
- return std::move(loaded_apk);
+ return std::unique_ptr<ApkAssets>(new ApkAssets(std::move(resources_asset),
+ std::move(loaded_arsc), std::move(assets),
+ property_flags, std::move(idmap_asset),
+ std::move(loaded_idmap)));
+}
+
+const std::string& ApkAssets::GetPath() const {
+ return assets_provider_->GetDebugName();
}
bool ApkAssets::IsUpToDate() const {
- if (IsLoader()) {
- // Loaders are invalidated by the app, not the system, so assume they are up to date.
- return true;
- }
- return (!loaded_idmap_ || loaded_idmap_->IsUpToDate()) &&
- last_mod_time_ == getFileModDate(path_.c_str());
+ // Loaders are invalidated by the app, not the system, so assume they are up to date.
+ return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
+ && assets_provider_->IsUpToDate());
}
-
} // namespace android
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
new file mode 100644
index 0000000..23cacf8
--- /dev/null
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -0,0 +1,398 @@
+/*
+ * 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 "androidfw/AssetsProvider.h"
+
+#include <sys/stat.h>
+
+#include <android-base/errors.h>
+#include <android-base/stringprintf.h>
+#include <android-base/utf8.h>
+#include <ziparchive/zip_archive.h>
+
+namespace android {
+namespace {
+constexpr const char* kEmptyDebugString = "<empty>";
+} // namespace
+
+std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
+ bool* file_exists) const {
+ return OpenInternal(path, mode, file_exists);
+}
+
+std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFile(const std::string& path) {
+ base::unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ if (!fd.ok()) {
+ LOG(ERROR) << "Failed to open file '" << path << "': " << base::SystemErrorCodeToString(errno);
+ return {};
+ }
+
+ return CreateAssetFromFd(std::move(fd), path.c_str());
+}
+
+std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFd(base::unique_fd fd,
+ const char* path,
+ off64_t offset,
+ off64_t length) {
+ CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
+ CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
+ << kUnknownLength;
+ if (length == kUnknownLength) {
+ length = lseek64(fd, 0, SEEK_END);
+ if (length < 0) {
+ LOG(ERROR) << "Failed to get size of file '" << ((path) ? path : "anon") << "': "
+ << base::SystemErrorCodeToString(errno);
+ return {};
+ }
+ }
+
+ incfs::IncFsFileMap file_map;
+ if (!file_map.Create(fd, offset, static_cast<size_t>(length), path)) {
+ LOG(ERROR) << "Failed to mmap file '" << ((path != nullptr) ? path : "anon") << "': "
+ << base::SystemErrorCodeToString(errno);
+ return {};
+ }
+
+ // If `path` is set, do not pass ownership of the `fd` to the new Asset since
+ // Asset::openFileDescriptor can use `path` to create new file descriptors.
+ return Asset::createFromUncompressedMap(std::move(file_map),
+ Asset::AccessMode::ACCESS_RANDOM,
+ (path != nullptr) ? base::unique_fd(-1) : std::move(fd));
+}
+
+ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path)
+ : value_(std::forward<std::string>(value)), is_path_(is_path) {}
+
+const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const {
+ return is_path_ ? &value_ : nullptr;
+}
+
+const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const {
+ return value_;
+}
+
+ZipAssetsProvider::ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path,
+ time_t last_mod_time)
+ : zip_handle_(handle, ::CloseArchive),
+ name_(std::forward<PathOrDebugName>(path)),
+ last_mod_time_(last_mod_time) {}
+
+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);
+ CloseArchive(handle);
+ return {};
+ }
+
+ struct stat sb{.st_mtime = -1};
+ if (stat(path.c_str(), &sb) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ LOG(WARNING) << "Failed to stat file '" << path << "': "
+ << base::SystemErrorCodeToString(errno);
+ }
+
+ return std::unique_ptr<ZipAssetsProvider>(
+ new ZipAssetsProvider(handle, PathOrDebugName{std::move(path),
+ true /* is_path */}, sb.st_mtime));
+}
+
+std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
+ std::string friendly_name,
+ off64_t offset,
+ off64_t len) {
+ ZipArchiveHandle handle;
+ const int released_fd = fd.release();
+ const int32_t result = (len == AssetsProvider::kUnknownLength)
+ ? ::OpenArchiveFd(released_fd, friendly_name.c_str(), &handle)
+ : ::OpenArchiveFdRange(released_fd, friendly_name.c_str(), &handle, len, offset);
+
+ if (result != 0) {
+ LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
+ << " and length " << len << ": " << ::ErrorCodeString(result);
+ CloseArchive(handle);
+ return {};
+ }
+
+ struct stat sb{.st_mtime = -1};
+ if (fstat(released_fd, &sb) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
+ << base::SystemErrorCodeToString(errno);
+ }
+
+ return std::unique_ptr<ZipAssetsProvider>(
+ new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name),
+ false /* is_path */}, sb.st_mtime));
+}
+
+std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
+ Asset::AccessMode mode,
+ bool* file_exists) const {
+ if (file_exists != nullptr) {
+ *file_exists = false;
+ }
+
+ ZipEntry entry;
+ if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
+ return {};
+ }
+
+ if (file_exists != nullptr) {
+ *file_exists = true;
+ }
+
+ const int fd = GetFileDescriptor(zip_handle_.get());
+ const off64_t fd_offset = GetFileDescriptorOffset(zip_handle_.get());
+ incfs::IncFsFileMap asset_map;
+ if (entry.method == kCompressDeflated) {
+ if (!asset_map.Create(fd, entry.offset + fd_offset, entry.compressed_length,
+ name_.GetDebugName().c_str())) {
+ LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName()
+ << "'";
+ return {};
+ }
+
+ std::unique_ptr<Asset> asset =
+ Asset::createFromCompressedMap(std::move(asset_map), entry.uncompressed_length, mode);
+ if (asset == nullptr) {
+ LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << name_.GetDebugName()
+ << "'";
+ return {};
+ }
+ return asset;
+ }
+
+ if (!asset_map.Create(fd, entry.offset + fd_offset, entry.uncompressed_length,
+ name_.GetDebugName().c_str())) {
+ LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
+ return {};
+ }
+
+ base::unique_fd ufd;
+ if (name_.GetPath() == nullptr) {
+ // If the zip name does not represent a path, create a new `fd` for the new Asset to own in
+ // order to create new file descriptors using Asset::openFileDescriptor. If the zip name is a
+ // path, it will be used to create new file descriptors.
+ ufd = base::unique_fd(dup(fd));
+ if (!ufd.ok()) {
+ LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << name_.GetDebugName() << "'";
+ return {};
+ }
+ }
+
+ auto asset = Asset::createFromUncompressedMap(std::move(asset_map), mode, std::move(ufd));
+ if (asset == nullptr) {
+ LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
+ return {};
+ }
+ return asset;
+}
+
+bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
+ const std::function<void(const StringPiece&, FileType)>& f)
+ const {
+ std::string root_path_full = root_path;
+ if (root_path_full.back() != '/') {
+ root_path_full += '/';
+ }
+
+ void* cookie;
+ if (StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
+ return false;
+ }
+
+ std::string name;
+ ::ZipEntry entry{};
+
+ // We need to hold back directories because many paths will contain them and we want to only
+ // surface one.
+ std::set<std::string> dirs{};
+
+ int32_t result;
+ while ((result = Next(cookie, &entry, &name)) == 0) {
+ StringPiece full_file_path(name);
+ StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
+
+ if (!leaf_file_path.empty()) {
+ auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
+ if (iter != leaf_file_path.end()) {
+ std::string dir =
+ leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+ dirs.insert(std::move(dir));
+ } else {
+ f(leaf_file_path, kFileTypeRegular);
+ }
+ }
+ }
+ EndIteration(cookie);
+
+ // Now present the unique directories.
+ for (const std::string& dir : dirs) {
+ f(dir, kFileTypeDirectory);
+ }
+
+ // -1 is end of iteration, anything else is an error.
+ return result == -1;
+}
+
+const std::string& ZipAssetsProvider::GetDebugName() const {
+ return name_.GetDebugName();
+}
+
+bool ZipAssetsProvider::IsUpToDate() const {
+ struct stat sb{};
+ if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
+ // If fstat fails on the zip archive, return true so the zip archive the resource system does
+ // attempt to refresh the ApkAsset.
+ return true;
+ }
+ return last_mod_time_ == sb.st_mtime;
+}
+
+DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
+ : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
+
+std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
+ struct stat sb{};
+ const int result = stat(path.c_str(), &sb);
+ if (result == -1) {
+ LOG(ERROR) << "Failed to find directory '" << path << "'.";
+ return nullptr;
+ }
+
+ if (!S_ISDIR(sb.st_mode)) {
+ LOG(ERROR) << "Path '" << path << "' is not a directory.";
+ return nullptr;
+ }
+
+ if (path[path.size() - 1] != OS_PATH_SEPARATOR) {
+ path += OS_PATH_SEPARATOR;
+ }
+
+ return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
+ sb.st_mtime));
+}
+
+std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
+ Asset::AccessMode /* mode */,
+ bool* file_exists) const {
+ const std::string resolved_path = dir_ + path;
+ if (file_exists != nullptr) {
+ struct stat sb{};
+ *file_exists = (stat(resolved_path.c_str(), &sb) != -1) && S_ISREG(sb.st_mode);
+ }
+
+ return CreateAssetFromFile(resolved_path);
+}
+
+bool DirectoryAssetsProvider::ForEachFile(
+ const std::string& /* root_path */,
+ const std::function<void(const StringPiece&, FileType)>& /* f */)
+ const {
+ return true;
+}
+
+const std::string& DirectoryAssetsProvider::GetDebugName() const {
+ return dir_;
+}
+
+bool DirectoryAssetsProvider::IsUpToDate() const {
+ struct stat sb{};
+ if (stat(dir_.c_str(), &sb) < 0) {
+ // If stat fails on the zip archive, return true so the zip archive the resource system does
+ // attempt to refresh the ApkAsset.
+ return true;
+ }
+ return last_mod_time_ == sb.st_mtime;
+}
+
+MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
+ std::unique_ptr<AssetsProvider>&& secondary)
+ : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)),
+ secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) {
+ if (primary_->GetDebugName() == kEmptyDebugString) {
+ debug_name_ = secondary_->GetDebugName();
+ } else if (secondary_->GetDebugName() == kEmptyDebugString) {
+ debug_name_ = primary_->GetDebugName();
+ } else {
+ debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName();
+ }
+}
+
+std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
+ std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
+ if (primary == nullptr || secondary == nullptr) {
+ return nullptr;
+ }
+ return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
+ std::move(secondary)));
+}
+
+std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path,
+ Asset::AccessMode mode,
+ bool* file_exists) const {
+ auto asset = primary_->Open(path, mode, file_exists);
+ return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists);
+}
+
+bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
+ const std::function<void(const StringPiece&, FileType)>& f)
+ const {
+ return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
+}
+
+const std::string& MultiAssetsProvider::GetDebugName() const {
+ return debug_name_;
+}
+
+bool MultiAssetsProvider::IsUpToDate() const {
+ return primary_->IsUpToDate() && secondary_->IsUpToDate();
+}
+
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() {
+ return std::make_unique<EmptyAssetsProvider>();
+}
+
+std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
+ Asset::AccessMode /* mode */,
+ bool* file_exists) const {
+ if (file_exists) {
+ *file_exists = false;
+ }
+ return nullptr;
+}
+
+bool EmptyAssetsProvider::ForEachFile(
+ const std::string& /* root_path */,
+ const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+ return true;
+}
+
+const std::string& EmptyAssetsProvider::GetDebugName() const {
+ const static std::string kEmpty = kEmptyDebugString;
+ return kEmpty;
+}
+
+bool EmptyAssetsProvider::IsUpToDate() const {
+ return true;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index adb383f95..f216f55 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -257,8 +257,8 @@
target_apk_path_(target_apk_path),
idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
-std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
- const StringPiece& idmap_data) {
+std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
+ const StringPiece& idmap_data) {
ATRACE_CALL();
size_t data_size = idmap_data.size();
auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 996b424..2a70f0d 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -767,10 +767,10 @@
return true;
}
-std::unique_ptr<const LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
- const size_t length,
- const LoadedIdmap* loaded_idmap,
- const package_property_t property_flags) {
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
+ const size_t length,
+ const LoadedIdmap* loaded_idmap,
+ const package_property_t property_flags) {
ATRACE_NAME("LoadedArsc::Load");
// Not using make_unique because the constructor is private.
@@ -799,11 +799,10 @@
}
}
- // Need to force a move for mingw32.
- return std::move(loaded_arsc);
+ return loaded_arsc;
}
-std::unique_ptr<const LoadedArsc> LoadedArsc::CreateEmpty() {
+std::unique_ptr<LoadedArsc> LoadedArsc::CreateEmpty() {
return std::unique_ptr<LoadedArsc>(new LoadedArsc());
}
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index e57490a..d0019ed 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -24,104 +24,49 @@
#include "android-base/unique_fd.h"
#include "androidfw/Asset.h"
+#include "androidfw/AssetsProvider.h"
#include "androidfw/Idmap.h"
#include "androidfw/LoadedArsc.h"
#include "androidfw/misc.h"
-struct ZipArchive;
-typedef ZipArchive* ZipArchiveHandle;
-
namespace android {
-class LoadedIdmap;
-
-// Interface for retrieving assets provided by an ApkAssets.
-class AssetsProvider {
- public:
- virtual ~AssetsProvider() = default;
-
- // Opens a file for reading.
- std::unique_ptr<Asset> Open(const std::string& path,
- Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM,
- bool* file_exists = nullptr) const {
- return OpenInternal(path, mode, file_exists);
- }
-
- // Iterate over all files and directories provided by the zip. The order of iteration is stable.
- virtual bool ForEachFile(const std::string& /* path */,
- const std::function<void(const StringPiece&, FileType)>& /* f */) const {
- return true;
- }
-
- protected:
- AssetsProvider() = default;
-
- virtual std::unique_ptr<Asset> OpenInternal(const std::string& path,
- Asset::AccessMode mode,
- bool* file_exists) const = 0;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(AssetsProvider);
-};
-
-class ZipAssetsProvider;
-
// Holds an APK.
class ApkAssets {
public:
- // This means the data extends to the end of the file.
- static constexpr off64_t kUnknownLength = -1;
- // Creates an ApkAssets.
- // If `system` is true, the package is marked as a system package, and allows some functions to
- // filter out this package when computing what configurations/resources are available.
- static std::unique_ptr<const ApkAssets> Load(
- const std::string& path, package_property_t flags = 0U,
- std::unique_ptr<const AssetsProvider> override_asset = nullptr);
+ // Creates an ApkAssets from a path on device.
+ static std::unique_ptr<ApkAssets> Load(const std::string& path,
+ package_property_t flags = 0U);
- // Creates an ApkAssets from the given file descriptor, and takes ownership of the file
- // descriptor. The `friendly_name` is some name that will be used to identify the source of
- // this ApkAssets in log messages and other debug scenarios.
- // If `length` equals kUnknownLength, offset must equal 0; otherwise, the apk data will be read
- // using the `offset` into the file descriptor and will be `length` bytes long.
- static std::unique_ptr<const ApkAssets> LoadFromFd(
- base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
- std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0,
- off64_t length = kUnknownLength);
+ // Creates an ApkAssets from an open file descriptor.
+ static std::unique_ptr<ApkAssets> LoadFromFd(base::unique_fd fd,
+ const std::string& debug_name,
+ package_property_t flags = 0U,
+ off64_t offset = 0,
+ off64_t len = AssetsProvider::kUnknownLength);
- // Creates an ApkAssets from the given path which points to a resources.arsc.
- static std::unique_ptr<const ApkAssets> LoadTable(
- const std::string& path, package_property_t flags = 0U,
- std::unique_ptr<const AssetsProvider> override_asset = nullptr);
+ // Creates an ApkAssets from an AssetProvider.
+ // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed.
+ static std::unique_ptr<ApkAssets> Load(std::unique_ptr<AssetsProvider> assets,
+ package_property_t flags = 0U);
- // Creates an ApkAssets from the given file descriptor which points to an resources.arsc, and
- // takes ownership of the file descriptor.
- // If `length` equals kUnknownLength, offset must equal 0; otherwise, the .arsc data will be read
- // using the `offset` into the file descriptor and will be `length` bytes long.
- static std::unique_ptr<const ApkAssets> LoadTableFromFd(
- base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
- std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0,
- off64_t length = kUnknownLength);
+ // Creates an ApkAssets from the given asset file representing a resources.arsc.
+ static std::unique_ptr<ApkAssets> LoadTable(std::unique_ptr<Asset> resources_asset,
+ std::unique_ptr<AssetsProvider> assets,
+ package_property_t flags = 0U);
// Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
// data.
- static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
- package_property_t flags = 0U);
+ static std::unique_ptr<ApkAssets> LoadOverlay(const std::string& idmap_path,
+ package_property_t flags = 0U);
- // Creates an ApkAssets from the directory path. File-based resources are read within the
- // directory as if the directory is an APK.
- static std::unique_ptr<const ApkAssets> LoadFromDir(
- const std::string& path, package_property_t flags = 0U,
- std::unique_ptr<const AssetsProvider> override_asset = nullptr);
-
- // Creates a totally empty ApkAssets with no resources table and no file entries.
- static std::unique_ptr<const ApkAssets> LoadEmpty(
- package_property_t flags = 0U,
- std::unique_ptr<const AssetsProvider> override_asset = nullptr);
-
- const std::string& GetPath() const {
- return path_;
- }
+ // TODO(177101983): Remove all uses of GetPath for checking whether two ApkAssets are the same.
+ // With the introduction of ResourcesProviders, not all ApkAssets have paths. This could cause
+ // bugs when path is used for comparison because multiple ApkAssets could have the same "firendly
+ // name". Use pointer equality instead. ResourceManager caches and reuses ApkAssets so the
+ // same asset should have the same pointer.
+ const std::string& GetPath() const;
const AssetsProvider* GetAssetsProvider() const {
return assets_provider_.get();
@@ -146,53 +91,40 @@
// Returns whether the resources.arsc is allocated in RAM (not mmapped).
bool IsTableAllocated() const {
- return resources_asset_ && resources_asset_->isAllocated();
+ return resources_asset_ != nullptr && resources_asset_->isAllocated();
}
bool IsUpToDate() const;
- // Creates an Asset from a file on disk.
- static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
-
- // Creates an Asset from a file descriptor.
- //
- // The asset takes ownership of the file descriptor. If `length` equals kUnknownLength, offset
- // must equal 0; otherwise, the asset data will be read using the `offset` into the file
- // descriptor and will be `length` bytes long.
- static std::unique_ptr<Asset> CreateAssetFromFd(base::unique_fd fd,
- const char* path,
- off64_t offset = 0,
- off64_t length = kUnknownLength);
private:
- DISALLOW_COPY_AND_ASSIGN(ApkAssets);
+ static std::unique_ptr<ApkAssets> LoadImpl(std::unique_ptr<AssetsProvider> assets,
+ package_property_t property_flags,
+ std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<LoadedIdmap> loaded_idmap);
- static std::unique_ptr<const ApkAssets> LoadImpl(
- std::unique_ptr<const AssetsProvider> assets, const std::string& path,
- package_property_t property_flags,
- std::unique_ptr<const AssetsProvider> override_assets = nullptr,
- std::unique_ptr<Asset> idmap_asset = nullptr,
- std::unique_ptr<const LoadedIdmap> idmap = nullptr);
+ static std::unique_ptr<ApkAssets> LoadImpl(std::unique_ptr<Asset> resources_asset,
+ std::unique_ptr<AssetsProvider> assets,
+ package_property_t property_flags,
+ std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<LoadedIdmap> loaded_idmap);
- static std::unique_ptr<const ApkAssets> LoadTableImpl(
- std::unique_ptr<Asset> resources_asset, const std::string& path,
- package_property_t property_flags,
- std::unique_ptr<const AssetsProvider> override_assets = nullptr);
+ ApkAssets(std::unique_ptr<Asset> resources_asset,
+ std::unique_ptr<LoadedArsc> loaded_arsc,
+ std::unique_ptr<AssetsProvider> assets,
+ package_property_t property_flags,
+ std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<LoadedIdmap> loaded_idmap);
- ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
- std::string path,
- time_t last_mod_time,
- package_property_t property_flags);
-
- std::unique_ptr<const AssetsProvider> assets_provider_;
- const std::string path_;
- time_t last_mod_time_;
- package_property_t property_flags_ = 0U;
std::unique_ptr<Asset> resources_asset_;
+ std::unique_ptr<LoadedArsc> loaded_arsc_;
+
+ std::unique_ptr<AssetsProvider> assets_provider_;
+ package_property_t property_flags_ = 0U;
+
std::unique_ptr<Asset> idmap_asset_;
- std::unique_ptr<const LoadedArsc> loaded_arsc_;
- std::unique_ptr<const LoadedIdmap> loaded_idmap_;
+ std::unique_ptr<LoadedIdmap> loaded_idmap_;
};
-} // namespace android
+} // namespace android
-#endif /* APKASSETS_H_ */
+#endif // APKASSETS_H_
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 80bae20..40c91a6 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -167,8 +167,8 @@
private:
/* AssetManager needs access to our "create" functions */
friend class AssetManager;
- friend class ApkAssets;
- friend class ZipAssetsProvider;
+ friend struct ZipAssetsProvider;
+ friend struct AssetsProvider;
/*
* Create the asset from a named file on disk.
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
new file mode 100644
index 0000000..7b06947
--- /dev/null
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -0,0 +1,185 @@
+/*
+ * 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 ANDROIDFW_ASSETSPROVIDER_H
+#define ANDROIDFW_ASSETSPROVIDER_H
+
+#include <memory>
+#include <string>
+
+#include "android-base/macros.h"
+#include "android-base/unique_fd.h"
+
+#include "androidfw/Asset.h"
+#include "androidfw/Idmap.h"
+#include "androidfw/LoadedArsc.h"
+#include "androidfw/misc.h"
+
+struct ZipArchive;
+
+namespace android {
+
+// Interface responsible for opening and iterating through asset files.
+struct AssetsProvider {
+ static constexpr off64_t kUnknownLength = -1;
+
+ // Opens a file for reading. If `file_exists` is not null, it will be set to `true` if the file
+ // exists. This is useful for determining if the file exists but was unable to be opened due to
+ // an I/O error.
+ std::unique_ptr<Asset> Open(const std::string& path,
+ Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM,
+ bool* file_exists = nullptr) const;
+
+ // Iterate over all files and directories provided by the interface. The order of iteration is
+ // stable.
+ virtual bool ForEachFile(const std::string& path,
+ const std::function<void(const StringPiece&, FileType)>& f) const = 0;
+
+ // Retrieves a name that represents the interface. This may or may not be the path of the
+ // interface source.
+ WARN_UNUSED virtual const std::string& GetDebugName() const = 0;
+
+ // Returns whether the interface provides the most recent version of its files.
+ WARN_UNUSED virtual bool IsUpToDate() const = 0;
+
+ // Creates an Asset from a file on disk.
+ static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+
+ // Creates an Asset from a file descriptor.
+ //
+ // The asset takes ownership of the file descriptor. If `length` equals kUnknownLength, offset
+ // must equal 0; otherwise, the asset data will be read using the `offset` into the file
+ // descriptor and will be `length` bytes long.
+ static std::unique_ptr<Asset> CreateAssetFromFd(base::unique_fd fd,
+ const char* path,
+ off64_t offset = 0,
+ off64_t length = AssetsProvider::kUnknownLength);
+
+ virtual ~AssetsProvider() = default;
+ protected:
+ virtual std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
+ bool* file_exists) const = 0;
+};
+
+// Supplies assets from a zip archive.
+struct ZipAssetsProvider : public AssetsProvider {
+ static std::unique_ptr<ZipAssetsProvider> Create(std::string path);
+ static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
+ std::string friendly_name,
+ off64_t offset = 0,
+ off64_t len = kUnknownLength);
+
+ bool ForEachFile(const std::string& root_path,
+ const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+ WARN_UNUSED const std::string& GetDebugName() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
+
+ ~ZipAssetsProvider() override = default;
+ protected:
+ std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
+ bool* file_exists) const override;
+
+ private:
+ struct PathOrDebugName;
+ ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, time_t last_mod_time);
+
+ struct PathOrDebugName {
+ PathOrDebugName(std::string&& value, bool is_path);
+
+ // Retrieves the path or null if this class represents a debug name.
+ WARN_UNUSED const std::string* GetPath() const;
+
+ // Retrieves a name that represents the interface. This may or may not represent a path.
+ WARN_UNUSED const std::string& GetDebugName() const;
+
+ private:
+ std::string value_;
+ bool is_path_;
+ };
+
+ std::unique_ptr<ZipArchive, void (*)(ZipArchive*)> zip_handle_;
+ PathOrDebugName name_;
+ time_t last_mod_time_;
+};
+
+// Supplies assets from a root directory.
+struct DirectoryAssetsProvider : public AssetsProvider {
+ static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir);
+
+ bool ForEachFile(const std::string& path,
+ const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+ WARN_UNUSED const std::string& GetDebugName() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
+
+ ~DirectoryAssetsProvider() override = default;
+ protected:
+ std::unique_ptr<Asset> OpenInternal(const std::string& path,
+ Asset::AccessMode mode,
+ bool* file_exists) const override;
+
+ private:
+ explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time);
+ std::string dir_;
+ time_t last_mod_time_;
+};
+
+// Supplies assets from a `primary` asset provider and falls back to supplying assets from the
+// `secondary` asset provider if the asset cannot be found in the `primary`.
+struct MultiAssetsProvider : public AssetsProvider {
+ static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary,
+ std::unique_ptr<AssetsProvider>&& secondary);
+
+ bool ForEachFile(const std::string& root_path,
+ const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+ WARN_UNUSED const std::string& GetDebugName() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
+
+ ~MultiAssetsProvider() override = default;
+ protected:
+ std::unique_ptr<Asset> OpenInternal(
+ const std::string& path, Asset::AccessMode mode, bool* file_exists) const override;
+
+ private:
+ MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
+ std::unique_ptr<AssetsProvider>&& secondary);
+
+ std::unique_ptr<AssetsProvider> primary_;
+ std::unique_ptr<AssetsProvider> secondary_;
+ std::string debug_name_;
+};
+
+// Does not provide any assets.
+struct EmptyAssetsProvider : public AssetsProvider {
+ static std::unique_ptr<AssetsProvider> Create();
+
+ bool ForEachFile(const std::string& path,
+ const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+ WARN_UNUSED const std::string& GetDebugName() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
+
+ ~EmptyAssetsProvider() override = default;
+ protected:
+ std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
+ bool* file_exists) const override;
+};
+
+} // namespace android
+
+#endif /* ANDROIDFW_ASSETSPROVIDER_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index fd9a8d13..0ded793 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -149,8 +149,8 @@
class LoadedIdmap {
public:
// Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
- static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_path,
- const StringPiece& idmap_data);
+ static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path,
+ const StringPiece& idmap_data);
// Returns the path to the IDMAP.
std::string_view IdmapPath() const {
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 891fb90..d9225cd 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -300,17 +300,14 @@
public:
// Load a resource table from memory pointed to by `data` of size `len`.
// The lifetime of `data` must out-live the LoadedArsc returned from this method.
- // If `system` is set to true, the LoadedArsc is considered as a system provided resource.
- // If `load_as_shared_library` is set to true, the application package (0x7f) is treated
- // as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an
- // ID.
- static std::unique_ptr<const LoadedArsc> Load(incfs::map_ptr<void> data,
- size_t length,
- const LoadedIdmap* loaded_idmap = nullptr,
- package_property_t property_flags = 0U);
+
+ static std::unique_ptr<LoadedArsc> Load(incfs::map_ptr<void> data,
+ size_t length,
+ const LoadedIdmap* loaded_idmap = nullptr,
+ package_property_t property_flags = 0U);
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
- static std::unique_ptr<const LoadedArsc> CreateEmpty();
+ static std::unique_ptr<LoadedArsc> CreateEmpty();
// Returns the string pool where all string resource values
// (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 3f0c7cb..b434915 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -27,6 +27,8 @@
#include "data/overlayable/R.h"
#include "data/system/R.h"
+using ::testing::NotNull;
+
namespace overlay = com::android::overlay;
namespace overlayable = com::android::overlayable;
@@ -195,7 +197,11 @@
}
TEST_F(IdmapTest, OverlayLoaderInterop) {
- auto loader_assets = ApkAssets::LoadTable("loader/resources.arsc", PROPERTY_LOADER);
+ auto asset = AssetsProvider::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+ ASSERT_THAT(asset, NotNull());
+
+ auto loader_assets = ApkAssets::LoadTable(std::move(asset), EmptyAssetsProvider::Create(),
+ PROPERTY_LOADER);
AssetManager2 asset_manager;
asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(),
overlay_assets_.get()});
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 9aa3634..f356c8130 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -339,10 +339,8 @@
}
TEST(LoadedArscTest, LoadCustomLoader) {
- std::string contents;
-
- std::unique_ptr<Asset>
- asset = ApkAssets::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+ auto asset = AssetsProvider::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+ ASSERT_THAT(asset, NotNull());
const StringPiece data(
reinterpret_cast<const char*>(asset->getBuffer(true /*wordAligned*/)),
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 615bf4d..ce1d96c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -435,6 +435,7 @@
"canvas/CanvasFrontend.cpp",
"canvas/CanvasOpBuffer.cpp",
"canvas/CanvasOpRasterizer.cpp",
+ "effects/StretchEffect.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/RenderNodeDrawable.cpp",
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 65f4e8c..e798f2a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -129,8 +129,9 @@
runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false);
- defaultRenderAhead = std::max(-1, std::min(2, base::GetIntProperty(PROPERTY_RENDERAHEAD,
- render_ahead().value_or(0))));
+ 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/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 96118aa..64b8b71 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -561,7 +561,7 @@
return;
}
c->concat(invertedMatrix);
- mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+ mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop);
} else {
c->drawDrawable(drawable.get());
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index aeb60e6..609706e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -23,6 +23,7 @@
#include "Outline.h"
#include "Rect.h"
#include "RevealClip.h"
+#include "effects/StretchEffect.h"
#include "utils/MathUtils.h"
#include "utils/PaintUtils.h"
@@ -98,6 +99,10 @@
SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
+ const StretchEffect& getStretchEffect() const { return mStretchEffect; }
+
+ StretchEffect& mutableStretchEffect() { return mStretchEffect; }
+
// Sets alpha, xfermode, and colorfilter from an SkPaint
// paint may be NULL, in which case defaults will be set
bool setFromPaint(const SkPaint* paint);
@@ -124,6 +129,7 @@
SkBlendMode mMode;
sk_sp<SkColorFilter> mColorFilter;
sk_sp<SkImageFilter> mImageFilter;
+ StretchEffect mStretchEffect;
};
/*
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/libs/hwui/effects/StretchEffect.cpp
similarity index 69%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to libs/hwui/effects/StretchEffect.cpp
index 19b20f2..51cbc75 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -14,7 +14,14 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+#include "StretchEffect.h"
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+namespace android::uirenderer {
+
+sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
+ // TODO: Implement & Cache
+ // Probably need to use mutable to achieve caching
+ return nullptr;
+}
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
new file mode 100644
index 0000000..7dfd639
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "utils/MathUtils.h"
+
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkImageFilter.h>
+
+namespace android::uirenderer {
+
+// TODO: Inherit from base RenderEffect type?
+class StretchEffect {
+public:
+ enum class StretchInterpolator {
+ SmoothStep,
+ };
+
+ bool isEmpty() const {
+ return MathUtils::isZero(stretchDirection.x())
+ && MathUtils::isZero(stretchDirection.y());
+ }
+
+ void setEmpty() {
+ *this = StretchEffect{};
+ }
+
+ void mergeWith(const StretchEffect& other) {
+ if (other.isEmpty()) {
+ return;
+ }
+ if (isEmpty()) {
+ *this = other;
+ return;
+ }
+ stretchDirection += other.stretchDirection;
+ if (isEmpty()) {
+ return setEmpty();
+ }
+ stretchArea.join(other.stretchArea);
+ maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
+ }
+
+ sk_sp<SkImageFilter> getImageFilter() const;
+
+ SkRect stretchArea {0, 0, 0, 0};
+ SkVector stretchDirection {0, 0};
+ float maxStretchAmount = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8b35d96..8023968 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -166,6 +166,31 @@
return true;
}
+static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ auto& stretch = renderNode->mutateStagingProperties()
+ .mutateLayerProperties().mutableStretchEffect();
+ if (stretch.isEmpty()) {
+ return false;
+ }
+ stretch.setEmpty();
+ renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+ return true;
+}
+
+static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
+ jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
+ StretchEffect{
+ .stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
+ .stretchDirection = {.fX = vX, .fY = vY},
+ .maxStretchAmount = max
+ });
+ renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+ return true;
+}
+
static jboolean android_view_RenderNode_hasShadow(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
return renderNode->stagingProperties().hasShadow();
@@ -678,6 +703,8 @@
{ "nSetOutlinePath", "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
{ "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty },
{ "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone },
+ { "nClearStretch", "(J)Z", (void*) android_view_RenderNode_clearStretch },
+ { "nStretch", "(JFFFFFFF)Z", (void*) android_view_RenderNode_stretch },
{ "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow },
{ "nSetSpotShadowColor", "(JI)Z", (void*) android_view_RenderNode_setSpotShadowColor },
{ "nGetSpotShadowColor", "(J)I", (void*) android_view_RenderNode_getSpotShadowColor },
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index c6c9e9d..71f533c 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -194,7 +194,7 @@
canvas->concat(invertedMatrix);
const SkIRect deviceBounds = canvas->getDeviceClipBounds();
- tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+ tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop);
}
}
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 75815bb6..c010212 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -20,6 +20,8 @@
#include "SkiaDisplayList.h"
#include "utils/TraceUtils.h"
+#include <include/effects/SkImageFilters.h>
+
#include <optional>
namespace android {
@@ -171,11 +173,25 @@
SkPaint* paint) {
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
- properties.getImageFilter() != nullptr) {
+ properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
paint->setAlpha(properties.alpha() * alphaMultiplier);
paint->setBlendMode(properties.xferMode());
paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
- paint->setImageFilter(sk_ref_sp(properties.getImageFilter()));
+
+ sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
+ sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
+ sk_sp<SkImageFilter> filter;
+ if (imageFilter && stretchFilter) {
+ filter = SkImageFilters::Compose(
+ std::move(stretchFilter),
+ std::move(imageFilter)
+ );
+ } else if (stretchFilter) {
+ filter = std::move(stretchFilter);
+ } else {
+ filter = std::move(imageFilter);
+ }
+ paint->setImageFilter(std::move(filter));
return true;
}
return false;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index eacabfd..37a6ee7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -157,12 +157,14 @@
void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
ATRACE_CALL();
- if (mRenderAheadDepth == 0 && DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
- mFixedRenderAhead = false;
- mRenderAheadCapacity = 1;
- } else {
- mFixedRenderAhead = true;
+ if (mFixedRenderAhead) {
mRenderAheadCapacity = mRenderAheadDepth;
+ } else {
+ if (DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
+ mRenderAheadCapacity = 1;
+ } else {
+ mRenderAheadCapacity = 0;
+ }
}
if (window) {
@@ -490,9 +492,11 @@
if (mNativeSurface) {
// TODO(b/165985262): measure performance impact
- if (const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
- vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
- native_window_set_frame_timeline_vsync(mNativeSurface->getNativeWindow(), vsyncId);
+ const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
+ const auto inputEventId = mCurrentFrameInfo->get(FrameInfoIndex::NewestInputEvent);
+ native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), vsyncId,
+ inputEventId);
}
}
@@ -762,11 +766,16 @@
}
void CanvasContext::setRenderAheadDepth(int renderAhead) {
- if (renderAhead > 2 || renderAhead < 0 || mNativeSurface) {
+ if (renderAhead > 2 || renderAhead < -1 || mNativeSurface) {
return;
}
- mFixedRenderAhead = true;
- mRenderAheadDepth = static_cast<uint32_t>(renderAhead);
+ if (renderAhead == -1) {
+ mFixedRenderAhead = false;
+ mRenderAheadDepth = 0;
+ } else {
+ mFixedRenderAhead = true;
+ mRenderAheadDepth = static_cast<uint32_t>(renderAhead);
+ }
}
SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 3d188c0..3dcaffb 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -31,6 +31,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -1379,10 +1380,9 @@
* Gets the Automatic Gain Control level in dB.
*
* <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
- * level may be used to indicate potential interference. When AGC is at a nominal level, this
- * value must be set as 0. Higher gain (and/or lower input power) shall be output as a positive
- * number. Hence in cases of strong jamming, in the band of this signal, this value will go more
- * negative.
+ * level may be used to indicate potential interference. Higher gain (and/or lower input power)
+ * shall be output as a positive number. Hence in cases of strong jamming, in the band of this
+ * signal, this value will go more negative.
*
* <p> Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
* components) may also affect the typical output of of this value on any given hardware design
@@ -1775,6 +1775,7 @@
*/
@Nullable
@SystemApi
+ @SuppressLint("NullableCollection")
public Collection<CorrelationVector> getCorrelationVectors() {
return mReadOnlyCorrelationVectors;
}
@@ -1785,7 +1786,9 @@
* @hide
*/
@TestApi
- public void setCorrelationVectors(@Nullable Collection<CorrelationVector> correlationVectors) {
+ public void setCorrelationVectors(
+ @SuppressLint("NullableCollection")
+ @Nullable Collection<CorrelationVector> correlationVectors) {
if (correlationVectors == null || correlationVectors.isEmpty()) {
resetCorrelationVectors();
} else {
diff --git a/location/java/android/location/ILocationListener.aidl b/location/java/android/location/ILocationListener.aidl
index ce92661..40d8615 100644
--- a/location/java/android/location/ILocationListener.aidl
+++ b/location/java/android/location/ILocationListener.aidl
@@ -16,7 +16,7 @@
package android.location;
-import android.location.LocationResult;
+import android.location.Location;
import android.os.IRemoteCallback;
/**
@@ -24,7 +24,7 @@
*/
oneway interface ILocationListener
{
- void onLocationChanged(in LocationResult locationResult, in @nullable IRemoteCallback onCompleteCallback);
+ void onLocationChanged(in List<Location> locations, in @nullable IRemoteCallback onCompleteCallback);
void onProviderEnabledChanged(String provider, boolean enabled);
void onFlushComplete(int requestCode);
}
diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java
index 523117b..35a4091 100644
--- a/location/java/android/location/LocationListener.java
+++ b/location/java/android/location/LocationListener.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.os.Bundle;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -48,15 +49,18 @@
/**
* Called when the location has changed and locations are being delivered in batches. The
* default implementation calls through to ({@link #onLocationChanged(Location)} with all
- * locations in the batch, from earliest to latest.
+ * locations in the batch. The list of locations is always guaranteed to be non-empty, and is
+ * always guaranteed to be ordered from earliest location to latest location (so that the
+ * earliest location in the batch is at index 0 in the list, and the latest location in the
+ * batch is at index size-1 in the list).
*
* @see LocationRequest#getMaxUpdateDelayMillis()
- * @param locationResult the location result list
+ * @param locations the location list
*/
- default void onLocationChanged(@NonNull LocationResult locationResult) {
- final int size = locationResult.size();
- for (int i = 0; i < size; ++i) {
- onLocationChanged(locationResult.get(i));
+ default void onLocationChanged(@NonNull List<Location> locations) {
+ final int size = locations.size();
+ for (int i = 0; i < size; i++) {
+ onLocationChanged(locations.get(i));
}
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 96ec590..d569482 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.LOCATION_HARDWARE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.location.GpsStatus.GPS_EVENT_STARTED;
import static android.location.LocationRequest.createFromDeprecatedCriteria;
import static android.location.LocationRequest.createFromDeprecatedProvider;
@@ -43,7 +44,6 @@
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -60,16 +60,17 @@
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.listeners.ListenerExecutor;
-import com.android.internal.listeners.ListenerTransportMultiplexer;
+import com.android.internal.listeners.ListenerTransport;
+import com.android.internal.listeners.ListenerTransportManager;
import com.android.internal.util.Preconditions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -231,19 +232,26 @@
/**
* Key used for an extra holding a {@link Location} value when a location change is sent using
- * a PendingIntent.
+ * a PendingIntent. If the location change includes a list of batched locations via
+ * {@link #KEY_LOCATIONS} then this key will still be present, and will hold the last location
+ * in the batch. Use {@link Intent#getParcelableExtra(String)} to retrieve the location.
*
* @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
*/
public static final String KEY_LOCATION_CHANGED = "location";
/**
- * Key used for an extra holding a {@link LocationResult} value when a location change is sent
- * using a PendingIntent.
+ * Key used for an extra holding a array of {@link Location}s when a location change is sent
+ * using a PendingIntent. This key will only be present if the location change includes
+ * multiple (ie, batched) locations, otherwise only {@link #KEY_LOCATION_CHANGED} will be
+ * present. Use {@link Intent#getParcelableArrayExtra(String)} to retrieve the locations.
+ *
+ * <p>The array of locations will never be empty, and will ordered from earliest location to
+ * latest location, the same as with {@link LocationListener#onLocationChanged(List)}.
*
* @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
*/
- public static final String KEY_LOCATION_RESULT = "locationResult";
+ public static final String KEY_LOCATIONS = "locations";
/**
* Key used for an extra holding an integer request code when location flush completion is sent
@@ -398,20 +406,40 @@
private static final long MAX_SINGLE_LOCATION_TIMEOUT_MS = 30 * 1000;
+ private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
+ "cache_key.location_enabled";
+
+ private static ILocationManager getService() throws RemoteException {
+ try {
+ return ILocationManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE));
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RemoteException(e);
+ }
+ }
+
@GuardedBy("sLocationListeners")
private static final WeakHashMap<LocationListener, WeakReference<LocationListenerTransport>>
sLocationListeners = new WeakHashMap<>();
- final Context mContext;
+ // allows lazy instantiation since most processes do not use GNSS APIs
+ private static class GnssLazyLoader {
+ static final GnssStatusTransportManager sGnssStatusListeners =
+ new GnssStatusTransportManager();
+ static final GnssNmeaTransportManager sGnssNmeaListeners =
+ new GnssNmeaTransportManager();
+ static final GnssMeasurementsTransportManager sGnssMeasurementsListeners =
+ new GnssMeasurementsTransportManager();
+ static final GnssAntennaTransportManager sGnssAntennaInfoListeners =
+ new GnssAntennaTransportManager();
+ static final GnssNavigationTransportManager sGnssNavigationListeners =
+ new GnssNavigationTransportManager();
+ }
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link "
- + "LocationManager}")
- final ILocationManager mService;
+ private final Context mContext;
+ private final ILocationManager mService;
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
+ private volatile PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
new PropertyInvalidatedCache<Integer, Boolean>(
4,
CACHE_KEY_LOCATION_ENABLED_PROPERTY) {
@@ -425,68 +453,12 @@
}
};
- @GuardedBy("mLock")
- @Nullable private GnssStatusTransportMultiplexer mGnssStatusTransportMultiplexer;
- @GuardedBy("mLock")
- @Nullable private GnssNmeaTransportMultiplexer mGnssNmeaTransportMultiplexer;
- @GuardedBy("mLock")
- @Nullable private GnssMeasurementsTransportMultiplexer mGnssMeasurementsTransportMultiplexer;
- @GuardedBy("mLock")
- @Nullable private GnssNavigationTransportMultiplexer mGnssNavigationTransportMultiplexer;
- @GuardedBy("mLock")
- @Nullable private GnssAntennaInfoTransportMultiplexer mGnssAntennaInfoTransportMultiplexer;
-
/**
* @hide
*/
public LocationManager(@NonNull Context context, @NonNull ILocationManager service) {
- mService = service;
- mContext = context;
- }
-
- private GnssStatusTransportMultiplexer getGnssStatusTransportMultiplexer() {
- synchronized (mLock) {
- if (mGnssStatusTransportMultiplexer == null) {
- mGnssStatusTransportMultiplexer = new GnssStatusTransportMultiplexer();
- }
- return mGnssStatusTransportMultiplexer;
- }
- }
-
- private GnssNmeaTransportMultiplexer getGnssNmeaTransportMultiplexer() {
- synchronized (mLock) {
- if (mGnssNmeaTransportMultiplexer == null) {
- mGnssNmeaTransportMultiplexer = new GnssNmeaTransportMultiplexer();
- }
- return mGnssNmeaTransportMultiplexer;
- }
- }
-
- private GnssMeasurementsTransportMultiplexer getGnssMeasurementsTransportMultiplexer() {
- synchronized (mLock) {
- if (mGnssMeasurementsTransportMultiplexer == null) {
- mGnssMeasurementsTransportMultiplexer = new GnssMeasurementsTransportMultiplexer();
- }
- return mGnssMeasurementsTransportMultiplexer;
- }
- }
-
- private GnssNavigationTransportMultiplexer getGnssNavigationTransportMultiplexer() {
- synchronized (mLock) {
- if (mGnssNavigationTransportMultiplexer == null) {
- mGnssNavigationTransportMultiplexer = new GnssNavigationTransportMultiplexer();
- }
- return mGnssNavigationTransportMultiplexer;
- }
- }
-
- private GnssAntennaInfoTransportMultiplexer getGnssAntennaInfoTransportMultiplexer() {
- synchronized (mLock) {
- if (mGnssAntennaInfoTransportMultiplexer == null) {
- mGnssAntennaInfoTransportMultiplexer = new GnssAntennaInfoTransportMultiplexer();
- }
- return mGnssAntennaInfoTransportMultiplexer;
- }
+ mContext = Objects.requireNonNull(context);
+ mService = Objects.requireNonNull(service);
}
/**
@@ -627,10 +599,9 @@
*/
@SystemApi
public boolean isLocationEnabledForUser(@NonNull UserHandle userHandle) {
- synchronized (mLock) {
- if (mLocationEnabledCache != null) {
- return mLocationEnabledCache.query(userHandle.getIdentifier());
- }
+ PropertyInvalidatedCache<Integer, Boolean> cache = mLocationEnabledCache;
+ if (cache != null) {
+ return cache.query(userHandle.getIdentifier());
}
// fallback if cache is disabled
@@ -1905,6 +1876,7 @@
@Deprecated
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
@Nullable
+ @SuppressWarnings("NullableCollection")
public List<String> getProviderPackages(@NonNull String provider) {
try {
return mService.getProviderPackages(provider);
@@ -2233,6 +2205,7 @@
* @see #getGnssCapabilities()
*/
@Nullable
+ @SuppressLint("NullableCollection")
public List<GnssAntennaInfo> getGnssAntennaInfos() {
try {
return mService.getGnssAntennaInfos();
@@ -2263,9 +2236,8 @@
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
- GnssStatusTransportMultiplexer multiplexer = getGnssStatusTransportMultiplexer();
- GnssStatus gnssStatus = multiplexer.getGnssStatus();
- int ttff = multiplexer.getTtff();
+ GnssStatus gnssStatus = GpsStatusTransport.sGnssStatus;
+ int ttff = GpsStatusTransport.sTtff;
if (gnssStatus != null) {
if (status == null) {
status = GpsStatus.create(gnssStatus, ttff);
@@ -2298,8 +2270,8 @@
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
- getGnssStatusTransportMultiplexer().addListener(listener,
- new HandlerExecutor(new Handler()));
+ GnssLazyLoader.sGnssStatusListeners.addListener(listener,
+ new GpsStatusTransport(new HandlerExecutor(new Handler()), mContext, listener));
return true;
}
@@ -2318,7 +2290,7 @@
"GpsStatus APIs not supported, please use GnssStatus APIs instead");
}
- getGnssStatusTransportMultiplexer().removeListener(listener);
+ GnssLazyLoader.sGnssStatusListeners.removeListener(listener);
}
/**
@@ -2381,7 +2353,8 @@
public boolean registerGnssStatusCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssStatus.Callback callback) {
- getGnssStatusTransportMultiplexer().addListener(callback, executor);
+ GnssLazyLoader.sGnssStatusListeners.addListener(callback,
+ new GnssStatusTransport(executor, mContext, callback));
return true;
}
@@ -2391,7 +2364,7 @@
* @param callback GNSS status callback object to remove
*/
public void unregisterGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
- getGnssStatusTransportMultiplexer().removeListener(callback);
+ GnssLazyLoader.sGnssStatusListeners.removeListener(callback);
}
/**
@@ -2471,7 +2444,8 @@
public boolean addNmeaListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnNmeaMessageListener listener) {
- getGnssNmeaTransportMultiplexer().addListener(listener, executor);
+ GnssLazyLoader.sGnssNmeaListeners.addListener(listener,
+ new GnssNmeaTransport(executor, mContext, listener));
return true;
}
@@ -2481,7 +2455,7 @@
* @param listener a {@link OnNmeaMessageListener} object to remove
*/
public void removeNmeaListener(@NonNull OnNmeaMessageListener listener) {
- getGnssNmeaTransportMultiplexer().removeListener(listener);
+ GnssLazyLoader.sGnssNmeaListeners.removeListener(listener);
}
/**
@@ -2597,10 +2571,8 @@
@NonNull GnssRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssMeasurementsEvent.Callback callback) {
- Preconditions.checkArgument(request != null, "invalid null request");
- getGnssMeasurementsTransportMultiplexer().addListener(request.toGnssMeasurementRequest(),
- callback, executor);
- return true;
+ return registerGnssMeasurementsCallback(request.toGnssMeasurementRequest(), executor,
+ callback);
}
/**
@@ -2622,8 +2594,8 @@
@NonNull GnssMeasurementRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssMeasurementsEvent.Callback callback) {
- Preconditions.checkArgument(request != null, "invalid null request");
- getGnssMeasurementsTransportMultiplexer().addListener(request, callback, executor);
+ GnssLazyLoader.sGnssMeasurementsListeners.addListener(callback,
+ new GnssMeasurementsTransport(executor, mContext, request, callback));
return true;
}
@@ -2655,7 +2627,7 @@
*/
public void unregisterGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
- getGnssMeasurementsTransportMultiplexer().removeListener(callback);
+ GnssLazyLoader.sGnssMeasurementsListeners.removeListener(callback);
}
/**
@@ -2680,7 +2652,8 @@
public boolean registerAntennaInfoListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssAntennaInfo.Listener listener) {
- getGnssAntennaInfoTransportMultiplexer().addListener(listener, executor);
+ GnssLazyLoader.sGnssAntennaInfoListeners.addListener(listener,
+ new GnssAntennaInfoTransport(executor, mContext, listener));
return true;
}
@@ -2693,7 +2666,7 @@
*/
@Deprecated
public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) {
- getGnssAntennaInfoTransportMultiplexer().removeListener(listener);
+ GnssLazyLoader.sGnssAntennaInfoListeners.removeListener(listener);
}
/**
@@ -2783,7 +2756,8 @@
public boolean registerGnssNavigationMessageCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssNavigationMessage.Callback callback) {
- getGnssNavigationTransportMultiplexer().addListener(callback, executor);
+ GnssLazyLoader.sGnssNavigationListeners.addListener(callback,
+ new GnssNavigationTransport(executor, mContext, callback));
return true;
}
@@ -2794,7 +2768,7 @@
*/
public void unregisterGnssNavigationMessageCallback(
@NonNull GnssNavigationMessage.Callback callback) {
- getGnssNavigationTransportMultiplexer().removeListener(callback);
+ GnssLazyLoader.sGnssNavigationListeners.removeListener(callback);
}
/**
@@ -2903,6 +2877,89 @@
}
}
+ private static class GnssStatusTransportManager extends
+ ListenerTransportManager<GnssStatusTransport> {
+
+ @Override
+ protected void registerTransport(GnssStatusTransport transport)
+ throws RemoteException {
+ getService().registerGnssStatusCallback(transport, transport.getPackage(),
+ transport.getAttributionTag());
+ }
+
+ @Override
+ protected void unregisterTransport(GnssStatusTransport transport)
+ throws RemoteException {
+ getService().unregisterGnssStatusCallback(transport);
+ }
+ }
+
+ private static class GnssNmeaTransportManager extends
+ ListenerTransportManager<GnssNmeaTransport> {
+
+ @Override
+ protected void registerTransport(GnssNmeaTransport transport)
+ throws RemoteException {
+ getService().registerGnssNmeaCallback(transport, transport.getPackage(),
+ transport.getAttributionTag());
+ }
+
+ @Override
+ protected void unregisterTransport(GnssNmeaTransport transport)
+ throws RemoteException {
+ getService().unregisterGnssNmeaCallback(transport);
+ }
+ }
+
+ private static class GnssMeasurementsTransportManager extends
+ ListenerTransportManager<GnssMeasurementsTransport> {
+
+ @Override
+ protected void registerTransport(GnssMeasurementsTransport transport)
+ throws RemoteException {
+ getService().addGnssMeasurementsListener(transport.getRequest(), transport,
+ transport.getPackage(), transport.getAttributionTag());
+ }
+
+ @Override
+ protected void unregisterTransport(GnssMeasurementsTransport transport)
+ throws RemoteException {
+ getService().removeGnssMeasurementsListener(transport);
+ }
+ }
+
+ private static class GnssAntennaTransportManager extends
+ ListenerTransportManager<GnssAntennaInfoTransport> {
+
+ @Override
+ protected void registerTransport(GnssAntennaInfoTransport transport) {
+ transport.getContext().registerReceiver(transport,
+ new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED));
+ }
+
+ @Override
+ protected void unregisterTransport(GnssAntennaInfoTransport transport) {
+ transport.getContext().unregisterReceiver(transport);
+ }
+ }
+
+ private static class GnssNavigationTransportManager extends
+ ListenerTransportManager<GnssNavigationTransport> {
+
+ @Override
+ protected void registerTransport(GnssNavigationTransport transport)
+ throws RemoteException {
+ getService().addGnssNavigationMessageListener(transport,
+ transport.getPackage(), transport.getAttributionTag());
+ }
+
+ @Override
+ protected void unregisterTransport(GnssNavigationTransport transport)
+ throws RemoteException {
+ getService().removeGnssNavigationMessageListener(transport);
+ }
+ }
+
private static class GetCurrentLocationTransport extends ILocationCallback.Stub implements
ListenerExecutor, CancellationSignal.OnCancelListener {
@@ -2968,12 +3025,12 @@
}
@Override
- public void onLocationChanged(LocationResult locationResult,
+ public void onLocationChanged(List<Location> locations,
@Nullable IRemoteCallback onCompleteCallback) {
executeSafely(mExecutor, () -> mListener, new ListenerOperation<LocationListener>() {
@Override
public void operate(LocationListener listener) {
- listener.onLocationChanged(locationResult);
+ listener.onLocationChanged(locations);
}
@Override
@@ -3017,7 +3074,7 @@
@Override
public void onStarted() {
- mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
+ mGpsListener.onGpsStatusChanged(GPS_EVENT_STARTED);
}
@Override
@@ -3036,273 +3093,269 @@
}
}
- private class GnssStatusTransportMultiplexer extends
- ListenerTransportMultiplexer<Void, GnssStatus.Callback> {
+ private static class GnssStatusTransport extends IGnssStatusListener.Stub implements
+ ListenerTransport<GnssStatus.Callback> {
- private @Nullable IGnssStatusListener mListenerTransport;
+ private final Executor mExecutor;
+ private final String mPackageName;
+ private final String mAttributionTag;
- volatile @Nullable GnssStatus mGnssStatus;
- volatile int mTtff;
+ private volatile @Nullable GnssStatus.Callback mListener;
- GnssStatusTransportMultiplexer() {}
-
- public GnssStatus getGnssStatus() {
- return mGnssStatus;
+ GnssStatusTransport(Executor executor, Context context, GnssStatus.Callback listener) {
+ Preconditions.checkArgument(executor != null, "invalid null executor");
+ Preconditions.checkArgument(listener != null, "invalid null callback");
+ mExecutor = executor;
+ mPackageName = context.getPackageName();
+ mAttributionTag = context.getAttributionTag();
+ mListener = listener;
}
- public int getTtff() {
- return mTtff;
+ public String getPackage() {
+ return mPackageName;
}
- public void addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor) {
- addListener(listener, null, new GpsAdapter(listener), executor);
+ public String getAttributionTag() {
+ return mAttributionTag;
}
@Override
- protected void registerWithServer(Void ignored) throws RemoteException {
- IGnssStatusListener transport = mListenerTransport;
- if (transport == null) {
- transport = new GnssStatusListener();
- }
-
- // if a remote exception is thrown the transport should not be set
- mListenerTransport = null;
- mService.registerGnssStatusCallback(transport, mContext.getPackageName(),
- mContext.getAttributionTag());
- mListenerTransport = transport;
+ public void unregister() {
+ mListener = null;
}
@Override
- protected void unregisterWithServer() throws RemoteException {
- if (mListenerTransport != null) {
- IGnssStatusListener transport = mListenerTransport;
- mListenerTransport = null;
- mService.unregisterGnssStatusCallback(transport);
- }
+ public @Nullable GnssStatus.Callback getListener() {
+ return mListener;
}
- private class GnssStatusListener extends IGnssStatusListener.Stub {
+ @Override
+ public void onGnssStarted() {
+ execute(mExecutor, GnssStatus.Callback::onStarted);
+ }
- GnssStatusListener() {}
+ @Override
+ public void onGnssStopped() {
+ execute(mExecutor, GnssStatus.Callback::onStopped);
+ }
- @Override
- public void onGnssStarted() {
- deliverToListeners(GnssStatus.Callback::onStarted);
- }
+ @Override
+ public void onFirstFix(int ttff) {
+ execute(mExecutor, listener -> listener.onFirstFix(ttff));
- @Override
- public void onGnssStopped() {
- deliverToListeners(GnssStatus.Callback::onStopped);
- }
+ }
- @Override
- public void onFirstFix(int ttff) {
- mTtff = ttff;
- deliverToListeners(callback -> callback.onFirstFix(ttff));
- }
+ @Override
+ public void onSvStatusChanged(GnssStatus gnssStatus) {
+ execute(mExecutor, listener -> listener.onSatelliteStatusChanged(gnssStatus));
+ }
+ }
- @Override
- public void onSvStatusChanged(GnssStatus gnssStatus) {
- mGnssStatus = gnssStatus;
- deliverToListeners(callback -> callback.onSatelliteStatusChanged(gnssStatus));
+ private static class GpsStatusTransport extends GnssStatusTransport {
+
+ static volatile int sTtff;
+ static volatile GnssStatus sGnssStatus;
+
+ GpsStatusTransport(Executor executor, Context context, GpsStatus.Listener listener) {
+ super(executor, context, new GpsAdapter(listener));
+ }
+
+ @Override
+ public void onFirstFix(int ttff) {
+ sTtff = ttff;
+ super.onFirstFix(ttff);
+ }
+
+ @Override
+ public void onSvStatusChanged(GnssStatus gnssStatus) {
+ sGnssStatus = gnssStatus;
+ super.onSvStatusChanged(gnssStatus);
+ }
+ }
+
+ private static class GnssNmeaTransport extends IGnssNmeaListener.Stub implements
+ ListenerTransport<OnNmeaMessageListener> {
+
+ private final Executor mExecutor;
+ private final String mPackageName;
+ private final String mAttributionTag;
+
+ private volatile @Nullable OnNmeaMessageListener mListener;
+
+ GnssNmeaTransport(Executor executor, Context context, OnNmeaMessageListener listener) {
+ Preconditions.checkArgument(executor != null, "invalid null executor");
+ Preconditions.checkArgument(listener != null, "invalid null listener");
+ mExecutor = executor;
+ mPackageName = context.getPackageName();
+ mAttributionTag = context.getAttributionTag();
+ mListener = listener;
+ }
+
+ public String getPackage() {
+ return mPackageName;
+ }
+
+ public String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ @Override
+ public void unregister() {
+ mListener = null;
+ }
+
+ @Override
+ public @Nullable OnNmeaMessageListener getListener() {
+ return mListener;
+ }
+
+ @Override
+ public void onNmeaReceived(long timestamp, String nmea) {
+ execute(mExecutor, callback -> callback.onNmeaMessage(nmea, timestamp));
+ }
+ }
+
+ private static class GnssMeasurementsTransport extends IGnssMeasurementsListener.Stub implements
+ ListenerTransport<GnssMeasurementsEvent.Callback> {
+
+ private final Executor mExecutor;
+ private final String mPackageName;
+ private final String mAttributionTag;
+ private final GnssMeasurementRequest mRequest;
+
+ private volatile @Nullable GnssMeasurementsEvent.Callback mListener;
+
+ GnssMeasurementsTransport(Executor executor, Context context,
+ GnssMeasurementRequest request, GnssMeasurementsEvent.Callback listener) {
+ Preconditions.checkArgument(executor != null, "invalid null executor");
+ Preconditions.checkArgument(listener != null, "invalid null callback");
+ Preconditions.checkArgument(request != null, "invalid null request");
+ mExecutor = executor;
+ mPackageName = context.getPackageName();
+ mAttributionTag = context.getAttributionTag();
+ mRequest = request;
+ mListener = listener;
+ }
+
+ public String getPackage() {
+ return mPackageName;
+ }
+
+ public String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ public GnssMeasurementRequest getRequest() {
+ return mRequest;
+ }
+
+ @Override
+ public void unregister() {
+ mListener = null;
+ }
+
+ @Override
+ public @Nullable GnssMeasurementsEvent.Callback getListener() {
+ return mListener;
+ }
+
+ @Override
+ public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
+ execute(mExecutor, callback -> callback.onGnssMeasurementsReceived(event));
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ execute(mExecutor, callback -> callback.onStatusChanged(status));
+ }
+ }
+
+ private static class GnssAntennaInfoTransport extends BroadcastReceiver implements
+ ListenerTransport<GnssAntennaInfo.Listener> {
+
+ private final Executor mExecutor;
+ private final Context mContext;
+
+ private volatile @Nullable GnssAntennaInfo.Listener mListener;
+
+ GnssAntennaInfoTransport(Executor executor, Context context,
+ GnssAntennaInfo.Listener listener) {
+ Preconditions.checkArgument(executor != null, "invalid null executor");
+ Preconditions.checkArgument(listener != null, "invalid null listener");
+ mExecutor = executor;
+ mContext = context;
+ mListener = listener;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public void unregister() {
+ mListener = null;
+ }
+
+ @Override
+ public @Nullable GnssAntennaInfo.Listener getListener() {
+ return mListener;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ArrayList<GnssAntennaInfo> infos = intent.getParcelableArrayListExtra(
+ EXTRA_GNSS_ANTENNA_INFOS);
+ if (infos != null) {
+ execute(mExecutor, callback -> callback.onGnssAntennaInfoReceived(infos));
}
}
}
- private class GnssNmeaTransportMultiplexer extends
- ListenerTransportMultiplexer<Void, OnNmeaMessageListener> {
+ private static class GnssNavigationTransport extends IGnssNavigationMessageListener.Stub
+ implements ListenerTransport<GnssNavigationMessage.Callback> {
- private @Nullable IGnssNmeaListener mListenerTransport;
+ private final Executor mExecutor;
+ private final String mPackageName;
+ private final String mAttributionTag;
- GnssNmeaTransportMultiplexer() {}
+ private volatile @Nullable GnssNavigationMessage.Callback mListener;
- public void addListener(@NonNull OnNmeaMessageListener listener,
- @NonNull Executor executor) {
- addListener(listener, null, listener, executor);
+ GnssNavigationTransport(Executor executor, Context context,
+ GnssNavigationMessage.Callback listener) {
+ Preconditions.checkArgument(executor != null, "invalid null executor");
+ Preconditions.checkArgument(listener != null, "invalid null callback");
+ mExecutor = executor;
+ mPackageName = context.getPackageName();
+ mAttributionTag = context.getAttributionTag();
+ mListener = listener;
+ }
+
+ public String getPackage() {
+ return mPackageName;
+ }
+
+ public String getAttributionTag() {
+ return mAttributionTag;
}
@Override
- protected void registerWithServer(Void ignored) throws RemoteException {
- IGnssNmeaListener transport = mListenerTransport;
- if (transport == null) {
- transport = new GnssNmeaListener();
- }
-
- // if a remote exception is thrown the transport should not be set
- mListenerTransport = null;
- mService.registerGnssNmeaCallback(transport, mContext.getPackageName(),
- mContext.getAttributionTag());
- mListenerTransport = transport;
+ public void unregister() {
+ mListener = null;
}
@Override
- protected void unregisterWithServer() throws RemoteException {
- if (mListenerTransport != null) {
- IGnssNmeaListener transport = mListenerTransport;
- mListenerTransport = null;
- mService.unregisterGnssNmeaCallback(transport);
- }
- }
-
- private class GnssNmeaListener extends IGnssNmeaListener.Stub {
-
- GnssNmeaListener() {}
-
- @Override
- public void onNmeaReceived(long timestamp, String nmea) {
- deliverToListeners(callback -> callback.onNmeaMessage(nmea, timestamp));
- }
- }
- }
-
- private class GnssMeasurementsTransportMultiplexer extends
- ListenerTransportMultiplexer<GnssMeasurementRequest, GnssMeasurementsEvent.Callback> {
-
- private @Nullable IGnssMeasurementsListener mListenerTransport;
-
- GnssMeasurementsTransportMultiplexer() {}
-
- @Override
- protected void registerWithServer(GnssMeasurementRequest request) throws RemoteException {
- IGnssMeasurementsListener transport = mListenerTransport;
- if (transport == null) {
- transport = new GnssMeasurementsListener();
- }
-
- // if a remote exception is thrown the transport should not be set
- mListenerTransport = null;
- mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(),
- mContext.getAttributionTag());
- mListenerTransport = transport;
+ public @Nullable GnssNavigationMessage.Callback getListener() {
+ return mListener;
}
@Override
- protected void unregisterWithServer() throws RemoteException {
- if (mListenerTransport != null) {
- IGnssMeasurementsListener transport = mListenerTransport;
- mListenerTransport = null;
- mService.removeGnssMeasurementsListener(transport);
- }
+ public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
+ execute(mExecutor, listener -> listener.onGnssNavigationMessageReceived(event));
}
@Override
- protected GnssMeasurementRequest mergeRequests(
- Collection<GnssMeasurementRequest> requests) {
- GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
- for (GnssMeasurementRequest request : requests) {
- if (request.isFullTracking()) {
- builder.setFullTracking(true);
- }
- if (request.isCorrelationVectorOutputsEnabled()) {
- builder.setCorrelationVectorOutputsEnabled(true);
- }
- }
-
- return builder.build();
- }
-
- private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
-
- GnssMeasurementsListener() {}
-
- @Override
- public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
- deliverToListeners(callback -> callback.onGnssMeasurementsReceived(event));
- }
-
- @Override
- public void onStatusChanged(int status) {
- deliverToListeners(callback -> callback.onStatusChanged(status));
- }
- }
- }
-
- private class GnssNavigationTransportMultiplexer extends
- ListenerTransportMultiplexer<Void, GnssNavigationMessage.Callback> {
-
- @Nullable
- private IGnssNavigationMessageListener mListenerTransport;
-
- GnssNavigationTransportMultiplexer() {}
-
- @Override
- protected void registerWithServer(Void ignored) throws RemoteException {
- IGnssNavigationMessageListener transport = mListenerTransport;
- if (transport == null) {
- transport = new GnssNavigationMessageListener();
- }
-
- // if a remote exception is thrown the transport should not be set
- mListenerTransport = null;
- mService.addGnssNavigationMessageListener(transport, mContext.getPackageName(),
- mContext.getAttributionTag());
- mListenerTransport = transport;
- }
-
- @Override
- protected void unregisterWithServer() throws RemoteException {
- if (mListenerTransport != null) {
- IGnssNavigationMessageListener transport = mListenerTransport;
- mListenerTransport = null;
- mService.removeGnssNavigationMessageListener(transport);
- }
- }
-
- private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub {
-
- GnssNavigationMessageListener() {}
-
- @Override
- public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
- deliverToListeners(listener -> listener.onGnssNavigationMessageReceived(event));
- }
-
- @Override
- public void onStatusChanged(int status) {
- deliverToListeners(listener -> listener.onStatusChanged(status));
- }
- }
- }
-
- private class GnssAntennaInfoTransportMultiplexer extends
- ListenerTransportMultiplexer<Void, GnssAntennaInfo.Listener> {
-
- private @Nullable BroadcastReceiver mListenerTransport;
-
- GnssAntennaInfoTransportMultiplexer() {}
-
- @Override
- protected void registerWithServer(Void ignored) {
- if (mListenerTransport == null) {
- // if an exception is thrown the transport should not be set
- BroadcastReceiver transport = new GnssAntennaInfoReceiver();
- mContext.registerReceiver(transport,
- new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED));
- mListenerTransport = transport;
- }
- }
-
- @Override
- protected void unregisterWithServer() {
- if (mListenerTransport != null) {
- BroadcastReceiver transport = mListenerTransport;
- mListenerTransport = null;
- mContext.unregisterReceiver(transport);
- }
- }
-
- private class GnssAntennaInfoReceiver extends BroadcastReceiver {
-
- GnssAntennaInfoReceiver() {}
-
- @Override
- public void onReceive(Context context, Intent intent) {
- ArrayList<GnssAntennaInfo> infos = intent.getParcelableArrayListExtra(
- EXTRA_GNSS_ANTENNA_INFOS);
- if (infos != null) {
- deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos));
- }
- }
+ public void onStatusChanged(int status) {
+ execute(mExecutor, listener -> listener.onStatusChanged(status));
}
}
@@ -3320,8 +3373,8 @@
}
@Override
- public void onLocationChanged(@NonNull LocationResult locationResult) {
- mCallback.onLocationBatch(locationResult.asList());
+ public void onLocationChanged(@NonNull List<Location> locations) {
+ mCallback.onLocationBatch(locations);
}
}
@@ -3335,12 +3388,6 @@
/**
* @hide
*/
- private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
- "cache_key.location_enabled";
-
- /**
- * @hide
- */
public static void invalidateLocalLocationEnabledCaches() {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_LOCATION_ENABLED_PROPERTY);
}
@@ -3349,8 +3396,6 @@
* @hide
*/
public void disableLocalLocationEnabledCaches() {
- synchronized (mLock) {
- mLocationEnabledCache = null;
- }
+ mLocationEnabledCache = null;
}
}
diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java
index 60b251e..26cf018 100644
--- a/location/java/android/location/LocationProvider.java
+++ b/location/java/android/location/LocationProvider.java
@@ -23,7 +23,9 @@
* Information about the properties of a location provider.
*
* @deprecated This class is incapable of representing unknown provider properties and may return
- * incorrect results when the properties are unknown.
+ * incorrect results on the rare occasion when a provider's properties are unknown. Prefer using
+ * {@link LocationManager#getProviderProperties(String)} to retrieve {@link ProviderProperties}
+ * instead.
*/
@Deprecated
public class LocationProvider {
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 323e740..cb56ee5 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -605,7 +605,7 @@
* When available, batching can provide substantial power savings to the device, and clients are
* encouraged to take advantage where appropriate for the use case.
*
- * @see LocationListener#onLocationChanged(LocationResult)
+ * @see LocationListener#onLocationChanged(java.util.List)
* @return the maximum time by which a location update may be delayed
*/
public @IntRange(from = 0) long getMaxUpdateDelayMillis() {
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 79a000c..8423000 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,7 +19,6 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,21 +33,14 @@
/**
* A location result representing a list of locations, ordered from earliest to latest.
+ *
+ * @hide
*/
public final class LocationResult implements Parcelable {
/**
- * Creates a new LocationResult from the given location.
- */
- public static @NonNull LocationResult create(@NonNull Location location) {
- ArrayList<Location> locations = new ArrayList<>(1);
- locations.add(new Location(Objects.requireNonNull(location)));
- return new LocationResult(locations);
- }
-
- /**
- * Creates a new LocationResult from the given locations. Locations must be ordered in the same
- * order they were derived (earliest to latest).
+ * Creates a new LocationResult from the given locations, making a copy of each location.
+ * Locations must be ordered in the same order they were derived (earliest to latest).
*/
public static @NonNull LocationResult create(@NonNull List<Location> locations) {
Preconditions.checkArgument(!locations.isEmpty());
@@ -60,16 +52,40 @@
}
/**
- * Creates a new LocationResult that takes ownership of the given location without copying it.
- * Callers must ensure the given location is never mutated after this method is called.
- *
- * @hide
+ * Creates a new LocationResult from the given locations, making a copy of each location.
+ * Locations must be ordered in the same order they were derived (earliest to latest).
*/
- @SystemApi
- public static @NonNull LocationResult wrap(@NonNull Location location) {
- ArrayList<Location> locations = new ArrayList<>(1);
- locations.add(Objects.requireNonNull(location));
- return new LocationResult(locations);
+ public static @NonNull LocationResult create(@NonNull Location... locations) {
+ Preconditions.checkArgument(locations.length > 0);
+ ArrayList<Location> locationsCopy = new ArrayList<>(locations.length);
+ for (Location location : locations) {
+ locationsCopy.add(new Location(Objects.requireNonNull(location)));
+ }
+ return new LocationResult(locationsCopy);
+ }
+
+ /**
+ * Creates a new LocationResult that takes ownership of the given locations without copying
+ * them. Callers must ensure the given locations are never mutated after this method is called.
+ * Locations must be ordered in the same order they were derived (earliest to latest).
+ */
+ public static @NonNull LocationResult wrap(@NonNull List<Location> locations) {
+ Preconditions.checkArgument(!locations.isEmpty());
+ return new LocationResult(new ArrayList<>(locations));
+ }
+
+ /**
+ * Creates a new LocationResult that takes ownership of the given locations without copying
+ * them. Callers must ensure the given locations are never mutated after this method is called.
+ * Locations must be ordered in the same order they were derived (earliest to latest).
+ */
+ public static @NonNull LocationResult wrap(@NonNull Location... locations) {
+ Preconditions.checkArgument(locations.length > 0);
+ ArrayList<Location> newLocations = new ArrayList<>(locations.length);
+ for (Location location : locations) {
+ newLocations.add(Objects.requireNonNull(location));
+ }
+ return new LocationResult(newLocations);
}
private final ArrayList<Location> mLocations;
@@ -112,7 +128,7 @@
}
/**
- * Returns the numer of locations in this location result.
+ * Returns the number of locations in this location result.
*/
public @IntRange(from = 1) int size() {
return mLocations.size();
@@ -139,9 +155,9 @@
* @hide
*/
public @NonNull LocationResult deepCopy() {
- ArrayList<Location> copy = new ArrayList<>(mLocations.size());
final int size = mLocations.size();
- for (int i = 0; i < size; ++i) {
+ ArrayList<Location> copy = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
copy.add(new Location(mLocations.get(i)));
}
return new LocationResult(copy);
@@ -164,7 +180,7 @@
* Returns a LocationResult with only locations that pass the given predicate. This
* implementation will avoid allocations when no locations are filtered out. The predicate is
* guaranteed to be invoked once per location, in order from earliest to latest. If all
- * locations are filtered out a null value is returned instead of an empty LocationResult.
+ * locations are filtered out a null value is returned.
*
* @hide
*/
diff --git a/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
index e3f51d9..50ed046 100644
--- a/location/java/android/location/provider/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -16,7 +16,7 @@
package android.location.provider;
-import android.location.LocationResult;
+import android.location.Location;
import android.location.provider.ProviderProperties;
/**
@@ -28,6 +28,7 @@
void onSetAllowed(boolean allowed);
void onSetProperties(in ProviderProperties properties);
- void onReportLocation(in LocationResult locationResult);
+ void onReportLocation(in Location location);
+ void onReportLocations(in List<Location> locations);
void onFlushComplete();
}
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 1306ea2..ae6395d 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -25,12 +25,13 @@
import android.content.Context;
import android.content.Intent;
import android.location.Location;
-import android.location.LocationResult;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -200,35 +201,29 @@
* Reports a new location from this provider.
*/
public void reportLocation(@NonNull Location location) {
- reportLocation(LocationResult.create(location));
+ ILocationProviderManager manager = mManager;
+ if (manager != null) {
+ try {
+ manager.onReportLocation(stripExtras(location));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
+ Log.w(mTag, e);
+ }
+ }
}
/**
- * Reports a new location result from this provider.
- *
- * <p>May only be used from Android S onwards.
+ * Reports a new batch of locations from this provider. Locations must be ordered in the list
+ * from earliest first to latest last.
*/
- public void reportLocation(@NonNull LocationResult locationResult) {
+ public void reportLocations(@NonNull List<Location> locations) {
ILocationProviderManager manager = mManager;
if (manager != null) {
- locationResult = locationResult.map(location -> {
- // remove deprecated extras to save on serialization costs
- Bundle extras = location.getExtras();
- if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION)
- || extras.containsKey("coarseLocation"))) {
- location = new Location(location);
- extras = location.getExtras();
- extras.remove(EXTRA_NO_GPS_LOCATION);
- extras.remove("coarseLocation");
- if (extras.isEmpty()) {
- location.setExtras(null);
- }
- }
- return location;
- });
+
try {
- manager.onReportLocation(locationResult);
+ manager.onReportLocations(stripExtras(locations));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
@@ -246,16 +241,61 @@
/**
* Requests a flush of any pending batched locations. The callback must always be invoked once
- * per invocation, and should be invoked after {@link #reportLocation(LocationResult)} has been
- * invoked with any flushed locations. The callback may be invoked immediately if no locations
- * are flushed.
+ * per invocation, and should be invoked after {@link #reportLocation(Location)} or
+ * {@link #reportLocations(List)} has been invoked with any flushed locations. The callback may
+ * be invoked immediately if no locations are flushed.
*/
public abstract void onFlush(@NonNull OnFlushCompleteCallback callback);
/**
* Implements optional custom commands.
*/
- public abstract void onSendExtraCommand(@NonNull String command, @Nullable Bundle extras);
+ public abstract void onSendExtraCommand(@NonNull String command,
+ @SuppressLint("NullableCollection")
+ @Nullable Bundle extras);
+
+ private static Location stripExtras(Location location) {
+ Bundle extras = location.getExtras();
+ if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION)
+ || extras.containsKey("indoorProbability")
+ || extras.containsKey("coarseLocation"))) {
+ location = new Location(location);
+ extras = location.getExtras();
+ extras.remove(EXTRA_NO_GPS_LOCATION);
+ extras.remove("indoorProbability");
+ extras.remove("coarseLocation");
+ if (extras.isEmpty()) {
+ location.setExtras(null);
+ }
+ }
+ return location;
+ }
+
+ private static List<Location> stripExtras(List<Location> locations) {
+ List<Location> mapped = locations;
+ final int size = locations.size();
+ int i = 0;
+ for (Location location : locations) {
+ Location newLocation = stripExtras(location);
+ if (mapped != locations) {
+ mapped.add(newLocation);
+ } else if (newLocation != location) {
+ mapped = new ArrayList<>(size);
+ int j = 0;
+ for (Location copiedLocation : locations) {
+ if (j >= i) {
+ break;
+ }
+ mapped.add(copiedLocation);
+ j++;
+ }
+ mapped.add(newLocation);
+ }
+ i++;
+ }
+
+ return mapped;
+ }
private final class Service extends ILocationProvider.Stub {
diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt
index 338d7cc..0d81f36 100644
--- a/location/lib/api/current.txt
+++ b/location/lib/api/current.txt
@@ -21,8 +21,8 @@
method @Deprecated protected void onInit();
method @Deprecated protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle);
method @Deprecated protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource);
- method @Deprecated public void reportLocation(android.location.Location);
- method @Deprecated public void reportLocation(android.location.LocationResult);
+ method @Deprecated public void reportLocation(@NonNull android.location.Location);
+ method @Deprecated public void reportLocations(@NonNull java.util.List<android.location.Location>);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setAdditionalProviderPackages(java.util.List<java.lang.String>);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public void setAllowed(boolean);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setEnabled(boolean);
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index aea93ce..7f1cf6d 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -16,13 +16,13 @@
package com.android.location.provider;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.location.ILocationManager;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.location.LocationResult;
import android.location.provider.ILocationProvider;
import android.location.provider.ILocationProviderManager;
import android.location.provider.ProviderProperties;
@@ -39,6 +39,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -94,10 +95,6 @@
*/
public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
- private static final String EXTRA_KEY_COARSE_LOCATION = "coarseLocation";
- private static final String EXTRA_KEY_NO_GPS_LOCATION = "noGPSLocation";
- private static final String EXTRA_KEY_INDOOR_PROB = "indoorProbability";
-
final String mTag;
@Nullable final String mPackageName;
@Nullable final String mAttributionTag;
@@ -254,20 +251,11 @@
/**
* Reports a new location from this provider.
*/
- public void reportLocation(Location location) {
- reportLocation(LocationResult.create(location));
- }
-
- /**
- * Reports a new location from this provider.
- */
- public void reportLocation(LocationResult locationResult) {
+ public void reportLocation(@NonNull Location location) {
ILocationProviderManager manager = mManager;
if (manager != null) {
- locationResult = locationResult.map(this::cleanUpExtras);
-
try {
- manager.onReportLocation(locationResult);
+ manager.onReportLocation(stripExtras(location));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
@@ -277,30 +265,20 @@
}
/**
- * Remove deprecated/unnecessary extras to save on serialization costs.
- *
- * {@link #EXTRA_KEY_NO_GPS_LOCATION} and {@link #EXTRA_KEY_COARSE_LOCATION} are deprecated.
- *
- * {@link #EXTRA_KEY_INDOOR_PROB} should only be used in the framework.
+ * Reports a new batch of locations from this provider. Locations must be ordered in the list
+ * from earliest first to latest last.
*/
- private Location cleanUpExtras(Location location) {
- Bundle extras = location.getExtras();
- if (extras == null) {
- return location;
- }
- if (extras.containsKey(EXTRA_KEY_NO_GPS_LOCATION)
- || extras.containsKey(EXTRA_KEY_COARSE_LOCATION)
- || extras.containsKey(EXTRA_KEY_INDOOR_PROB)) {
- location = new Location(location);
- extras = location.getExtras();
- extras.remove(EXTRA_KEY_NO_GPS_LOCATION);
- extras.remove(EXTRA_KEY_COARSE_LOCATION);
- extras.remove(EXTRA_KEY_INDOOR_PROB);
- if (extras.isEmpty()) {
- location.setExtras(null);
+ public void reportLocations(@NonNull List<Location> locations) {
+ ILocationProviderManager manager = mManager;
+ if (manager != null) {
+ try {
+ manager.onReportLocations(stripExtras(locations));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
+ Log.w(mTag, e);
}
}
- return location;
}
protected void onInit() {
@@ -336,9 +314,9 @@
/**
* Requests a flush of any pending batched locations. The callback must always be invoked once
- * per invocation, and should be invoked after {@link #reportLocation(LocationResult)} has been
- * invoked with any flushed locations. The callback may be invoked immediately if no locations
- * are flushed.
+ * per invocation, and should be invoked after {@link #reportLocation(Location)} or
+ * {@link #reportLocations(List)} has been invoked with any flushed locations. The callback may
+ * be invoked immediately if no locations are flushed.
*/
protected void onFlush(OnFlushCompleteCallback callback) {
callback.onFlushComplete();
@@ -433,4 +411,47 @@
onSendExtraCommand(command, extras);
}
}
+
+ private static Location stripExtras(Location location) {
+ Bundle extras = location.getExtras();
+ if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION)
+ || extras.containsKey("indoorProbability")
+ || extras.containsKey("coarseLocation"))) {
+ location = new Location(location);
+ extras = location.getExtras();
+ extras.remove(EXTRA_NO_GPS_LOCATION);
+ extras.remove("indoorProbability");
+ extras.remove("coarseLocation");
+ if (extras.isEmpty()) {
+ location.setExtras(null);
+ }
+ }
+ return location;
+ }
+
+ private static List<Location> stripExtras(List<Location> locations) {
+ List<Location> mapped = locations;
+ final int size = locations.size();
+ int i = 0;
+ for (Location location : locations) {
+ Location newLocation = stripExtras(location);
+ if (mapped != locations) {
+ mapped.add(newLocation);
+ } else if (newLocation != location) {
+ mapped = new ArrayList<>(size);
+ int j = 0;
+ for (Location copiedLocation : locations) {
+ if (j >= i) {
+ break;
+ }
+ mapped.add(copiedLocation);
+ j++;
+ }
+ mapped.add(newLocation);
+ }
+ i++;
+ }
+
+ return mapped;
+ }
}
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bcc846b..bb1dbd4 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -739,6 +739,13 @@
if (mBundle != null) {
aa.mBundle = new Bundle(mBundle);
}
+
+ // Allow the FLAG_HW_HOTWORD only for AudioSource.VOICE_RECOGNITION
+ if (mSource != MediaRecorder.AudioSource.VOICE_RECOGNITION
+ && (mFlags & FLAG_HW_HOTWORD) == FLAG_HW_HOTWORD) {
+ aa.mFlags &= ~FLAG_HW_HOTWORD;
+ }
+
return aa;
}
@@ -852,6 +859,23 @@
}
/**
+ * @hide
+ * Request for capture in hotword mode.
+ *
+ * Requests an audio path optimized for Hotword detection use cases from
+ * the low power audio DSP. This is valid only for capture with
+ * audio source {@link MediaRecorder.AudioSource#VOICE_RECOGNITION}.
+ * There is no guarantee that this mode is available on the device.
+ * @return the same Builder instance.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+ public @NonNull Builder setHotwordMode() {
+ mFlags |= FLAG_HW_HOTWORD;
+ return this;
+ }
+
+ /**
* Specifies whether the audio may or may not be captured by other apps or the system.
*
* The default is {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}.
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 255c15c..eedb996 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -316,6 +316,15 @@
* Not guaranteed to be supported by devices, may be emulated if not supported. */
public static final int ENCODING_PCM_32BIT = 22;
+ /** Audio data format: MPEG-H baseline profile, level 3 */
+ public static final int ENCODING_MPEGH_BL_L3 = 23;
+ /** Audio data format: MPEG-H baseline profile, level 4 */
+ public static final int ENCODING_MPEGH_BL_L4 = 24;
+ /** Audio data format: MPEG-H low complexity profile, level 3 */
+ public static final int ENCODING_MPEGH_LC_L3 = 25;
+ /** Audio data format: MPEG-H low complexity profile, level 4 */
+ public static final int ENCODING_MPEGH_LC_L4 = 26;
+
/** @hide */
public static String toLogFriendlyEncoding(int enc) {
switch(enc) {
@@ -363,6 +372,14 @@
return "ENCODING_PCM_24BIT_PACKED";
case ENCODING_PCM_32BIT:
return "ENCODING_PCM_32BIT";
+ case ENCODING_MPEGH_BL_L3:
+ return "ENCODING_MPEGH_BL_L3";
+ case ENCODING_MPEGH_BL_L4:
+ return "ENCODING_MPEGH_BL_L4";
+ case ENCODING_MPEGH_LC_L3:
+ return "ENCODING_MPEGH_LC_L3";
+ case ENCODING_MPEGH_LC_L4:
+ return "ENCODING_MPEGH_LC_L4";
default :
return "invalid encoding " + enc;
}
@@ -594,6 +611,10 @@
case ENCODING_OPUS:
case ENCODING_PCM_24BIT_PACKED:
case ENCODING_PCM_32BIT:
+ case ENCODING_MPEGH_BL_L3:
+ case ENCODING_MPEGH_BL_L4:
+ case ENCODING_MPEGH_LC_L3:
+ case ENCODING_MPEGH_LC_L4:
return true;
default:
return false;
@@ -625,6 +646,10 @@
case ENCODING_OPUS:
case ENCODING_PCM_24BIT_PACKED:
case ENCODING_PCM_32BIT:
+ case ENCODING_MPEGH_BL_L3:
+ case ENCODING_MPEGH_BL_L4:
+ case ENCODING_MPEGH_LC_L3:
+ case ENCODING_MPEGH_LC_L4:
return true;
default:
return false;
@@ -659,6 +684,10 @@
case ENCODING_E_AC3_JOC:
case ENCODING_DOLBY_MAT:
case ENCODING_OPUS:
+ case ENCODING_MPEGH_BL_L3:
+ case ENCODING_MPEGH_BL_L4:
+ case ENCODING_MPEGH_LC_L3:
+ case ENCODING_MPEGH_LC_L4:
return false;
case ENCODING_INVALID:
default:
@@ -693,6 +722,10 @@
case ENCODING_E_AC3_JOC:
case ENCODING_DOLBY_MAT:
case ENCODING_OPUS:
+ case ENCODING_MPEGH_BL_L3:
+ case ENCODING_MPEGH_BL_L4:
+ case ENCODING_MPEGH_LC_L3:
+ case ENCODING_MPEGH_LC_L4:
return false;
case ENCODING_INVALID:
default:
@@ -975,6 +1008,10 @@
case ENCODING_OPUS:
case ENCODING_PCM_24BIT_PACKED:
case ENCODING_PCM_32BIT:
+ case ENCODING_MPEGH_BL_L3:
+ case ENCODING_MPEGH_BL_L4:
+ case ENCODING_MPEGH_LC_L3:
+ case ENCODING_MPEGH_LC_L4:
mEncoding = encoding;
break;
case ENCODING_INVALID:
@@ -1197,7 +1234,11 @@
ENCODING_DOLBY_MAT,
ENCODING_OPUS,
ENCODING_PCM_24BIT_PACKED,
- ENCODING_PCM_32BIT }
+ ENCODING_PCM_32BIT,
+ ENCODING_MPEGH_BL_L3,
+ ENCODING_MPEGH_BL_L4,
+ ENCODING_MPEGH_LC_L3,
+ ENCODING_MPEGH_LC_L4 }
)
@Retention(RetentionPolicy.SOURCE)
public @interface Encoding {}
@@ -1213,6 +1254,10 @@
ENCODING_AC4,
ENCODING_E_AC3_JOC,
ENCODING_DOLBY_MAT,
+ ENCODING_MPEGH_BL_L3,
+ ENCODING_MPEGH_BL_L4,
+ ENCODING_MPEGH_LC_L3,
+ ENCODING_MPEGH_LC_L4,
};
/** @hide */
@@ -1225,7 +1270,11 @@
ENCODING_DOLBY_TRUEHD,
ENCODING_AC4,
ENCODING_E_AC3_JOC,
- ENCODING_DOLBY_MAT }
+ ENCODING_DOLBY_MAT,
+ ENCODING_MPEGH_BL_L3,
+ ENCODING_MPEGH_BL_L4,
+ ENCODING_MPEGH_LC_L3,
+ ENCODING_MPEGH_LC_L4 }
)
@Retention(RetentionPolicy.SOURCE)
public @interface SurroundSoundEncoding {}
@@ -1259,6 +1308,14 @@
return "Dolby Atmos in Dolby Digital Plus";
case ENCODING_DOLBY_MAT:
return "Dolby MAT";
+ case ENCODING_MPEGH_BL_L3:
+ return "MPEG-H 3D Audio baseline profile level 3";
+ case ENCODING_MPEGH_BL_L4:
+ return "MPEG-H 3D Audio baseline profile level 4";
+ case ENCODING_MPEGH_LC_L3:
+ return "MPEG-H 3D Audio low complexity profile level 3";
+ case ENCODING_MPEGH_LC_L4:
+ return "MPEG-H 3D Audio low complexity profile level 4";
default:
return "Unknown surround sound format";
}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index c2ce7d3..b196437 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
@@ -67,7 +68,14 @@
* fill with the new audio data. The size of this buffer, specified during the construction,
* determines how long an AudioRecord can record before "over-running" data that has not
* been read yet. Data should be read from the audio hardware in chunks of sizes inferior to
- * the total recording buffer size.
+ * the total recording buffer size.</p>
+ * <p>
+ * Applications creating an AudioRecord instance need
+ * {@link android.Manifest.permission#RECORD_AUDIO} or the Builder will throw
+ * {@link java.lang.UnsupportedOperationException} on
+ * {@link android.media.AudioRecord.Builder#build build()},
+ * and the constructor will return an instance in state
+ * {@link #STATE_UNINITIALIZED}.</p>
*/
public class AudioRecord implements AudioRouting, MicrophoneDirection,
AudioRecordingMonitor, AudioRecordingMonitorClient
@@ -297,6 +305,7 @@
* smaller than getMinBufferSize() will result in an initialization failure.
* @throws java.lang.IllegalArgumentException
*/
+ @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)
throws IllegalArgumentException {
@@ -334,6 +343,7 @@
* @throws IllegalArgumentException
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int sessionId) throws IllegalArgumentException {
mRecordingState = RECORDSTATE_STOPPED;
@@ -718,6 +728,7 @@
* were incompatible, or if they are not supported by the device,
* or if the device was not available.
*/
+ @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
public AudioRecord build() throws UnsupportedOperationException {
if (mAudioPlaybackCaptureConfiguration != null) {
return buildAudioPlaybackCaptureRecord();
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 9657b25e..4d7ed11 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1089,7 +1089,7 @@
private int[] mSampleRates;
private Range<Integer>[] mSampleRateRanges;
- private int mMaxInputChannelCount;
+ private Range<Integer>[] mInputChannelRanges;
private static final int MAX_INPUT_CHANNEL_COUNT = 30;
@@ -1119,11 +1119,61 @@
}
/**
- * Returns the maximum number of input channels supported. The codec
- * supports any number of channels between 1 and this maximum value.
+ * Returns the maximum number of input channels supported.
+ *
+ * Through {@link android.os.Build.VERSION_CODES#R}, this method indicated support
+ * for any number of input channels between 1 and this maximum value.
+ *
+ * As of {@link android.os.Build.VERSION_CODES#S},
+ * the implied lower limit of 1 channel is no longer valid.
+ * As of {@link android.os.Build.VERSION_CODES#S}, {@link #getMaxInputChannelCount} is
+ * superseded by {@link #getInputChannelCountRanges},
+ * which returns an array of ranges of channels.
+ * The {@link #getMaxInputChannelCount} method will return the highest value
+ * in the ranges returned by {@link #getInputChannelCountRanges}
+ *
*/
public int getMaxInputChannelCount() {
- return mMaxInputChannelCount;
+ int overall_max = 0;
+ for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+ int lmax = mInputChannelRanges[i].getUpper();
+ if (lmax > overall_max) {
+ overall_max = lmax;
+ }
+ }
+ return overall_max;
+ }
+
+ /**
+ * Returns the minimum number of input channels supported.
+ * This is often 1, but does vary for certain mime types.
+ *
+ * This returns the lowest channel count in the ranges returned by
+ * {@link #getInputChannelCountRanges}.
+ */
+ public int getMinInputChannelCount() {
+ int overall_min = MAX_INPUT_CHANNEL_COUNT;
+ for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+ int lmin = mInputChannelRanges[i].getLower();
+ if (lmin < overall_min) {
+ overall_min = lmin;
+ }
+ }
+ return overall_min;
+ }
+
+ /*
+ * Returns an array of ranges representing the number of input channels supported.
+ * The codec supports any number of input channels within this range.
+ *
+ * This supersedes the {@link #getMaxInputChannelCount} method.
+ *
+ * For many codecs, this will be a single range [1..N], for some N.
+ */
+ @SuppressLint("ArrayReturn")
+ @NonNull
+ public Range<Integer>[] getInputChannelCountRanges() {
+ return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
}
/* no public constructor */
@@ -1146,7 +1196,7 @@
private void initWithPlatformLimits() {
mBitrateRange = Range.create(0, Integer.MAX_VALUE);
- mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
+ mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
// mBitrateRange = Range.create(1, 320000);
final int minSampleRate = SystemProperties.
getInt("ro.mediacodec.min_sample_rate", 7350);
@@ -1158,9 +1208,12 @@
private boolean supports(Integer sampleRate, Integer inputChannels) {
// channels and sample rates are checked orthogonally
- if (inputChannels != null &&
- (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
- return false;
+ if (inputChannels != null) {
+ int ix = Utils.binarySearchDistinctRanges(
+ mInputChannelRanges, inputChannels);
+ if (ix < 0) {
+ return false;
+ }
}
if (sampleRate != null) {
int ix = Utils.binarySearchDistinctRanges(
@@ -1292,12 +1345,28 @@
} else if (sampleRateRange != null) {
limitSampleRates(new Range[] { sampleRateRange });
}
- applyLimits(maxChannels, bitRates);
+
+ Range<Integer> channelRange = Range.create(1, maxChannels);
+
+ applyLimits(new Range[] { channelRange }, bitRates);
}
- private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
- mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
- .clamp(maxInputChannels);
+ private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
+
+ // clamp & make a local copy
+ Range<Integer>[] myInputChannels = new Range[inputChannels.length];
+ for (int i = 0; i < inputChannels.length; i++) {
+ int lower = inputChannels[i].clamp(1);
+ int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
+ myInputChannels[i] = Range.create(lower, upper);
+ }
+
+ // sort, intersect with existing, & save channel list
+ sortDistinctRanges(myInputChannels);
+ Range<Integer>[] joinedChannelList =
+ intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
+ mInputChannelRanges = joinedChannelList;
+
if (bitRates != null) {
mBitrateRange = mBitrateRange.intersect(bitRates);
}
@@ -1305,6 +1374,7 @@
private void parseFromInfo(MediaFormat info) {
int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+ Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
Range<Integer> bitRates = POSITIVE_INTEGERS;
if (info.containsKey("sample-rate-ranges")) {
@@ -1315,17 +1385,38 @@
}
limitSampleRates(rateRanges);
}
- if (info.containsKey("max-channel-count")) {
+
+ // we will prefer channel-ranges over max-channel-count
+ if (info.containsKey("channel-ranges")) {
+ String[] channelStrings = info.getString("channel-ranges").split(",");
+ Range<Integer>[] channelRanges = new Range[channelStrings.length];
+ for (int i = 0; i < channelStrings.length; i++) {
+ channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
+ }
+ channels = channelRanges;
+ } else if (info.containsKey("channel-range")) {
+ Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
+ null);
+ channels = new Range[] { oneRange };
+ } else if (info.containsKey("max-channel-count")) {
maxInputChannels = Utils.parseIntSafely(
info.getString("max-channel-count"), maxInputChannels);
+ if (maxInputChannels == 0) {
+ channels = new Range[] {Range.create(0, 0)};
+ } else {
+ channels = new Range[] {Range.create(1, maxInputChannels)};
+ }
} else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
maxInputChannels = 0;
+ channels = new Range[] {Range.create(0, 0)};
}
+
if (info.containsKey("bitrate-range")) {
bitRates = bitRates.intersect(
Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
}
- applyLimits(maxInputChannels, bitRates);
+
+ applyLimits(channels, bitRates);
}
/** @hide */
@@ -1334,7 +1425,7 @@
if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
}
- if (mMaxInputChannelCount == 1) {
+ if (getMaxInputChannelCount() == 1) {
// mono-only format
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 68237de..5e732f9 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -879,38 +879,37 @@
/**
* Interface for receiving events about media routing changes.
*/
- public static class Callback {
-
+ public interface Callback {
/**
* Called when routes are added.
* @param routes the list of routes that have been added. It's never empty.
*/
- public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
+ default void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when routes are removed.
* @param routes the list of routes that have been removed. It's never empty.
*/
- public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
+ default void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when routes are changed.
* @param routes the list of routes that have been changed. It's never empty.
*/
- public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+ default void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when a session is changed.
* @param session the updated session
*/
- public void onSessionUpdated(@NonNull RoutingSessionInfo session) {}
+ default void onSessionUpdated(@NonNull RoutingSessionInfo session) {}
/**
* Called when a session is released.
* @param session the released session.
* @see #releaseSession(RoutingSessionInfo)
*/
- public void onSessionReleased(@NonNull RoutingSessionInfo session) {}
+ default void onSessionReleased(@NonNull RoutingSessionInfo session) {}
/**
* Called when media is transferred.
@@ -918,13 +917,13 @@
* @param oldSession the previous session
* @param newSession the new session or {@code null} if the session is released.
*/
- public void onTransferred(@NonNull RoutingSessionInfo oldSession,
+ default void onTransferred(@NonNull RoutingSessionInfo oldSession,
@Nullable RoutingSessionInfo newSession) { }
/**
* Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
*/
- public void onTransferFailed(@NonNull RoutingSessionInfo session,
+ default void onTransferFailed(@NonNull RoutingSessionInfo session,
@NonNull MediaRoute2Info route) { }
/**
@@ -933,7 +932,7 @@
* @param packageName the package name of the application
* @param preferredFeatures the list of preferred route features set by an application.
*/
- public void onPreferredFeaturesChanged(@NonNull String packageName,
+ default void onPreferredFeaturesChanged(@NonNull String packageName,
@NonNull List<String> preferredFeatures) {}
/**
@@ -946,7 +945,7 @@
* {@link MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
* {@link MediaRoute2ProviderService#REASON_INVALID_COMMAND},
*/
- public void onRequestFailed(int reason) {}
+ default void onRequestFailed(int reason) {}
}
final class CallbackRecord {
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionManager.java b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
index 6bbcfd3..b183eaf 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionManager.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
@@ -90,7 +91,9 @@
* supplied bundle
*/
void onRecognitionSucceeded(@NonNull RecognitionRequest recognitionRequest,
- @NonNull MediaMetadata result, @Nullable Bundle extras);
+ @NonNull MediaMetadata result,
+ @SuppressLint("NullableCollection")
+ @Nullable Bundle extras);
/**
* Invoked when the search is not successful (possibly but not necessarily due to error).
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionService.java b/media/java/android/media/musicrecognition/MusicRecognitionService.java
index e2071b8..04b4c39b 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionService.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
@@ -53,7 +54,9 @@
* @param extras extra data to be supplied back to the caller. Note that all executable
* parameters and file descriptors would be removed from the supplied bundle
*/
- void onRecognitionSucceeded(@NonNull MediaMetadata result, @Nullable Bundle extras);
+ void onRecognitionSucceeded(@NonNull MediaMetadata result,
+ @SuppressLint("NullableCollection")
+ @Nullable Bundle extras);
/**
* Call this method if the search does not find a result on an error occurred.
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 4d835b2..8525e99 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
import android.hardware.tv.tuner.V1_0.Constants;
@@ -1017,6 +1018,7 @@
* failed.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public List<FrontendInfo> getAvailableFrontendInfos() {
FrontendInfo[] feInfoList = getFrontendInfoListInternal();
if (feInfoList == null) {
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index c064de2..08a8d89 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,7 +18,6 @@
#include <binder/Parcel.h>
#include <jni.h>
-#include <media/IMediaMetricsService.h>
#include <media/MediaMetricsItem.h>
#include <nativehelper/JNIHelp.h>
#include <variant>
@@ -151,12 +150,7 @@
return (jint)BAD_VALUE;
}
- sp<IMediaMetricsService> service = mediametrics::BaseItem::getService();
- if (service == nullptr) {
- ALOGW("Cannot retrieve mediametrics service");
- return (jint)NO_INIT;
- }
- return (jint)service->submitBuffer((char *)buffer, length);
+ return (jint)mediametrics::BaseItem::submitBuffer((char *)buffer, length);
}
// Helper function to convert a native PersistableBundle to a Java
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index bdc8a4f..6b78817 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -22,16 +22,27 @@
#include "FilterClient.h"
-using ::aidl::android::media::tv::tuner::TunerFilterAvSettings;
+using ::aidl::android::media::tv::tuner::TunerDemuxIpAddressSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterAlpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterIpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterMmtpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterMonitorEvent;
+using ::aidl::android::media::tv::tuner::TunerFilterScIndexMask;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionBits;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionCondition;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionTableInfo;
using ::aidl::android::media::tv::tuner::TunerFilterSharedHandleInfo;
+using ::aidl::android::media::tv::tuner::TunerFilterTlvConfiguration;
using ::aidl::android::media::tv::tuner::TunerFilterTsConfiguration;
-using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
using ::android::hardware::tv::tuner::V1_0::DemuxStreamId;
+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::ScramblingStatus;
namespace android {
@@ -294,6 +305,10 @@
return Status::fromServiceSpecificError(static_cast<int32_t>(Result::INVALID_STATE));
}
+ if (filterEvents.size() == 0) {
+ return Status::fromServiceSpecificError(static_cast<int32_t>(Result::INVALID_ARGUMENT));
+ }
+
DemuxFilterEvent event;
DemuxFilterEventExt eventExt;
getHidlFilterEvent(filterEvents, event, eventExt);
@@ -310,80 +325,569 @@
TunerFilterConfiguration FilterClient::getAidlFilterSettings(DemuxFilterSettings configure) {
TunerFilterConfiguration config;
- // TODO: complete filter setting conversion
switch (configure.getDiscriminator()) {
- case DemuxFilterSettings::hidl_discriminator::ts: {
- TunerFilterSettings filterSettings;
- switch (configure.ts().filterSettings.getDiscriminator()) {
- case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::av: {
- TunerFilterAvSettings av{
- .isPassthrough = configure.ts().filterSettings.av().isPassthrough,
- };
- filterSettings.set<TunerFilterSettings::av>(av);
- break;
- }
- default:
- break;
- }
-
- TunerFilterTsConfiguration ts{
- .tpid = configure.ts().tpid,
- .filterSettings = filterSettings,
- };
- config.set<TunerFilterConfiguration::ts>(ts);
-
- return config;
- }
+ case DemuxFilterSettings::hidl_discriminator::ts:
+ return getAidlTsSettings(configure.ts());
case DemuxFilterSettings::hidl_discriminator::mmtp:
- break;
+ return getAidlMmtpSettings(configure.mmtp());
case DemuxFilterSettings::hidl_discriminator::ip:
- break;
+ return getAidlIpSettings(configure.ip());
case DemuxFilterSettings::hidl_discriminator::tlv:
- break;
+ return getAidlTlvSettings(configure.tlv());
+ case DemuxFilterSettings::hidl_discriminator::alp:
+ return getAidlAlpSettings(configure.alp());
default:
break;
}
+ ALOGE("Wrong DemuxFilterSettings union.");
return config;
}
+TunerFilterConfiguration FilterClient::getAidlTsSettings(DemuxTsFilterSettings ts) {
+ TunerFilterConfiguration config;
+ TunerFilterSettings filterSettings;
+ switch (ts.filterSettings.getDiscriminator()) {
+ case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::av: {
+ filterSettings.set<TunerFilterSettings::av>(
+ getAidlAvSettings(ts.filterSettings.av()));
+ break;
+ }
+ case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::section: {
+ filterSettings.set<TunerFilterSettings::section>(
+ getAidlSectionSettings(ts.filterSettings.section()));
+ break;
+ }
+ case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::pesData: {
+ filterSettings.set<TunerFilterSettings::pesData>(
+ getAidlPesDataSettings(ts.filterSettings.pesData()));
+ break;
+ }
+ case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::record: {
+ filterSettings.set<TunerFilterSettings::record>(
+ getAidlRecordSettings(ts.filterSettings.record()));
+ break;
+ }
+ default:
+ filterSettings.set<TunerFilterSettings::nothing>(true);
+ break;
+ }
-void TunerFilterCallback::getHidlFilterEvent(const vector<TunerFilterEvent>& filterEvents,
- DemuxFilterEvent& event, DemuxFilterEventExt& /*eventExt*/) {
- // TODO: finish handling extended evets and other filter event types
- switch (filterEvents[0].getTag()) {
- case TunerFilterEvent::media: {
- for (int i = 0; i < filterEvents.size(); i++) {
- hidl_handle handle = hidl_handle(
- makeFromAidl(filterEvents[i].get<TunerFilterEvent::media>().avMemory));
- int size = event.events.size();
- event.events.resize(size + 1);
- event.events[size].media({
- .avMemory = handle,
- .streamId = static_cast<DemuxStreamId>(
- filterEvents[i].get<TunerFilterEvent::media>().streamId),
- .isPtsPresent =
- filterEvents[i].get<TunerFilterEvent::media>().isPtsPresent,
- .pts = static_cast<uint64_t>(
- filterEvents[i].get<TunerFilterEvent::media>().pts),
- .dataLength = static_cast<uint32_t>(
- filterEvents[i].get<TunerFilterEvent::media>().dataLength),
- .offset = static_cast<uint32_t>(
- filterEvents[i].get<TunerFilterEvent::media>().offset),
- .isSecureMemory =
- filterEvents[i].get<TunerFilterEvent::media>().isSecureMemory,
- .avDataId = static_cast<uint64_t>(
- filterEvents[i].get<TunerFilterEvent::media>().avDataId),
- .mpuSequenceNumber = static_cast<uint32_t>(
- filterEvents[i].get<TunerFilterEvent::media>().offset),
- .isPesPrivateData =
- filterEvents[i].get<TunerFilterEvent::media>().isPesPrivateData,
- });
- }
+ TunerFilterTsConfiguration aidlTs{
+ .tpid = static_cast<char16_t>(ts.tpid),
+ .filterSettings = filterSettings,
+ };
+ config.set<TunerFilterConfiguration::ts>(aidlTs);
+
+ return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlMmtpSettings(DemuxMmtpFilterSettings mmtp) {
+ TunerFilterConfiguration config;
+ TunerFilterSettings filterSettings;
+ switch (mmtp.filterSettings.getDiscriminator()) {
+ case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::av: {
+ filterSettings.set<TunerFilterSettings::av>(
+ getAidlAvSettings(mmtp.filterSettings.av()));
+ break;
+ }
+ case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::section: {
+ filterSettings.set<TunerFilterSettings::section>(
+ getAidlSectionSettings(mmtp.filterSettings.section()));
+ break;
+ }
+ case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::pesData: {
+ filterSettings.set<TunerFilterSettings::pesData>(
+ getAidlPesDataSettings(mmtp.filterSettings.pesData()));
+ break;
+ }
+ case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::record: {
+ filterSettings.set<TunerFilterSettings::record>(
+ getAidlRecordSettings(mmtp.filterSettings.record()));
+ break;
+ }
+ case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::download: {
+ filterSettings.set<TunerFilterSettings::download>(
+ getAidlDownloadSettings(mmtp.filterSettings.download()));
+ break;
+ }
+ default:
+ filterSettings.set<TunerFilterSettings::nothing>(true);
+ break;
+ }
+
+ TunerFilterMmtpConfiguration aidlMmtp{
+ .mmtpPid = static_cast<char16_t>(mmtp.mmtpPid),
+ .filterSettings = filterSettings,
+ };
+ config.set<TunerFilterConfiguration::mmtp>(aidlMmtp);
+
+ return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlIpSettings(DemuxIpFilterSettings ip) {
+ TunerFilterConfiguration config;
+ TunerFilterSettings filterSettings;
+ switch (ip.filterSettings.getDiscriminator()) {
+ case DemuxIpFilterSettings::FilterSettings::hidl_discriminator::section: {
+ filterSettings.set<TunerFilterSettings::section>(
+ getAidlSectionSettings(ip.filterSettings.section()));
+ break;
+ }
+ case DemuxIpFilterSettings::FilterSettings::hidl_discriminator::bPassthrough: {
+ filterSettings.set<TunerFilterSettings::isPassthrough>(
+ ip.filterSettings.bPassthrough());
+ break;
+ }
+ default:
+ filterSettings.set<TunerFilterSettings::nothing>(true);
+ break;
+ }
+
+ TunerDemuxIpAddressSettings ipAddr{
+ .srcPort = static_cast<char16_t>(ip.ipAddr.srcPort),
+ .dstPort = static_cast<char16_t>(ip.ipAddr.dstPort),
+ };
+ getAidlIpAddress(ip.ipAddr, ipAddr.srcIpAddress, ipAddr.dstIpAddress);
+
+ TunerFilterIpConfiguration aidlIp{
+ .ipAddr = ipAddr,
+ .filterSettings = filterSettings,
+ };
+ config.set<TunerFilterConfiguration::ip>(aidlIp);
+
+ return config;
+}
+
+void FilterClient::getAidlIpAddress(DemuxIpAddress ipAddr,
+ TunerDemuxIpAddress& srcIpAddress, TunerDemuxIpAddress& dstIpAddress) {
+ switch (ipAddr.srcIpAddress.getDiscriminator()) {
+ case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
+ int size = ipAddr.srcIpAddress.v4().size();
+ srcIpAddress.isIpV6 = false;
+ srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+ copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
+ srcIpAddress.addr.begin());
+ break;
+ }
+ case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v6: {
+ int size = ipAddr.srcIpAddress.v6().size();
+ srcIpAddress.isIpV6 = true;
+ srcIpAddress.addr.resize(size);
+ copy(&ipAddr.srcIpAddress.v6()[0], &ipAddr.srcIpAddress.v6()[size],
+ srcIpAddress.addr.begin());
break;
}
default:
break;
}
+ switch (ipAddr.dstIpAddress.getDiscriminator()) {
+ case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
+ int size = ipAddr.dstIpAddress.v4().size();
+ dstIpAddress.isIpV6 = false;
+ dstIpAddress.addr.resize(size);
+ copy(&ipAddr.dstIpAddress.v4()[0], &ipAddr.dstIpAddress.v4()[size],
+ dstIpAddress.addr.begin());
+ break;
+ }
+ case DemuxIpAddress::DstIpAddress::hidl_discriminator::v6: {
+ int size = ipAddr.dstIpAddress.v6().size();
+ dstIpAddress.isIpV6 = true;
+ dstIpAddress.addr.resize(size);
+ copy(&ipAddr.dstIpAddress.v6()[0], &ipAddr.dstIpAddress.v6()[size],
+ dstIpAddress.addr.begin());
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+TunerFilterConfiguration FilterClient::getAidlTlvSettings(DemuxTlvFilterSettings tlv) {
+ TunerFilterConfiguration config;
+ TunerFilterSettings filterSettings;
+ switch (tlv.filterSettings.getDiscriminator()) {
+ case DemuxTlvFilterSettings::FilterSettings::hidl_discriminator::section: {
+ filterSettings.set<TunerFilterSettings::section>(
+ getAidlSectionSettings(tlv.filterSettings.section()));
+ break;
+ }
+ case DemuxTlvFilterSettings::FilterSettings::hidl_discriminator::bPassthrough: {
+ filterSettings.set<TunerFilterSettings::isPassthrough>(
+ tlv.filterSettings.bPassthrough());
+ break;
+ }
+ default:
+ filterSettings.set<TunerFilterSettings::nothing>(true);
+ break;
+ }
+
+ TunerFilterTlvConfiguration aidlTlv{
+ .packetType = static_cast<int8_t>(tlv.packetType),
+ .isCompressedIpPacket = tlv.isCompressedIpPacket,
+ .filterSettings = filterSettings,
+ };
+ config.set<TunerFilterConfiguration::tlv>(aidlTlv);
+
+ return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlAlpSettings(DemuxAlpFilterSettings alp) {
+ TunerFilterConfiguration config;
+ TunerFilterSettings filterSettings;
+ switch (alp.filterSettings.getDiscriminator()) {
+ case DemuxAlpFilterSettings::FilterSettings::hidl_discriminator::section: {
+ filterSettings.set<TunerFilterSettings::section>(
+ getAidlSectionSettings(alp.filterSettings.section()));
+ break;
+ }
+ default:
+ filterSettings.set<TunerFilterSettings::nothing>(true);
+ break;
+ }
+
+ TunerFilterAlpConfiguration aidlAlp{
+ .packetType = static_cast<int8_t>(alp.packetType),
+ .lengthType = static_cast<int8_t>(alp.lengthType),
+ .filterSettings = filterSettings,
+ };
+ config.set<TunerFilterConfiguration::alp>(aidlAlp);
+
+ return config;
+}
+
+TunerFilterAvSettings FilterClient::getAidlAvSettings(DemuxFilterAvSettings hidlAv) {
+ TunerFilterAvSettings aidlAv{
+ .isPassthrough = hidlAv.isPassthrough,
+ };
+ return aidlAv;
+}
+
+TunerFilterSectionSettings FilterClient::getAidlSectionSettings(
+ DemuxFilterSectionSettings hidlSection) {
+ TunerFilterSectionSettings aidlSection;
+
+ switch (hidlSection.condition.getDiscriminator()) {
+ case DemuxFilterSectionSettings::Condition::hidl_discriminator::sectionBits: {
+ TunerFilterSectionBits sectionBits;
+ auto hidlSectionBits = hidlSection.condition.sectionBits();
+ sectionBits.filter.resize(hidlSectionBits.filter.size());
+ sectionBits.mask.resize(hidlSectionBits.mask.size());
+ sectionBits.mode.resize(hidlSectionBits.mode.size());
+ copy(hidlSectionBits.filter.begin(), hidlSectionBits.filter.end(),
+ hidlSectionBits.filter.begin());
+ copy(hidlSectionBits.mask.begin(), hidlSectionBits.mask.end(),
+ hidlSectionBits.mask.begin());
+ copy(hidlSectionBits.mode.begin(), hidlSectionBits.mode.end(),
+ hidlSectionBits.mode.begin());
+ aidlSection.condition.set<TunerFilterSectionCondition::sectionBits>(sectionBits);
+ break;
+ }
+ case DemuxFilterSectionSettings::Condition::hidl_discriminator::tableInfo: {
+ TunerFilterSectionTableInfo tableInfo{
+ .tableId = static_cast<char16_t>(hidlSection.condition.tableInfo().tableId),
+ .version = static_cast<char16_t>(hidlSection.condition.tableInfo().version),
+ };
+ aidlSection.condition.set<TunerFilterSectionCondition::tableInfo>(tableInfo);
+ break;
+ }
+ }
+ aidlSection.isCheckCrc = hidlSection.isCheckCrc;
+ aidlSection.isRepeat = hidlSection.isRepeat;
+ aidlSection.isRaw = hidlSection.isRaw;
+ return aidlSection;
+}
+
+TunerFilterPesDataSettings FilterClient::getAidlPesDataSettings(
+ DemuxFilterPesDataSettings hidlPesData) {
+ TunerFilterPesDataSettings aidlPesData{
+ .streamId = static_cast<char16_t>(hidlPesData.streamId),
+ .isRaw = hidlPesData.isRaw,
+ };
+ return aidlPesData;
+}
+
+TunerFilterRecordSettings FilterClient::getAidlRecordSettings(
+ DemuxFilterRecordSettings hidlRecord) {
+ TunerFilterScIndexMask mask;
+ switch (hidlRecord.scIndexMask.getDiscriminator()) {
+ case DemuxFilterRecordSettings::ScIndexMask::hidl_discriminator::sc: {
+ mask.set<TunerFilterScIndexMask::sc>(hidlRecord.scIndexMask.sc());
+ break;
+ }
+ case DemuxFilterRecordSettings::ScIndexMask::hidl_discriminator::scHevc: {
+ mask.set<TunerFilterScIndexMask::scHevc>(hidlRecord.scIndexMask.scHevc());
+ break;
+ }
+ default:
+ break;
+ }
+ TunerFilterRecordSettings aidlRecord{
+ .tsIndexMask = static_cast<int32_t>(hidlRecord.tsIndexMask),
+ .scIndexType = static_cast<int32_t>(hidlRecord.scIndexType),
+ .scIndexMask = mask,
+ };
+ return aidlRecord;
+}
+
+TunerFilterDownloadSettings FilterClient::getAidlDownloadSettings(
+ DemuxFilterDownloadSettings hidlDownload) {
+ TunerFilterDownloadSettings aidlDownload{
+ .downloadId = static_cast<int32_t>(hidlDownload.downloadId),
+ };
+ return aidlDownload;
+}
+
+void TunerFilterCallback::getHidlFilterEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) {
+ switch (filterEvents[0].getTag()) {
+ case TunerFilterEvent::media: {
+ getHidlMediaEvent(filterEvents, event);
+ break;
+ }
+ case TunerFilterEvent::section: {
+ getHidlSectionEvent(filterEvents, event);
+ break;
+ }
+ case TunerFilterEvent::pes: {
+ getHidlPesEvent(filterEvents, event);
+ break;
+ }
+ case TunerFilterEvent::tsRecord: {
+ getHidlTsRecordEvent(filterEvents, event, eventExt);
+ break;
+ }
+ case TunerFilterEvent::mmtpRecord: {
+ getHidlMmtpRecordEvent(filterEvents, event, eventExt);
+ break;
+ }
+ case TunerFilterEvent::download: {
+ getHidlDownloadEvent(filterEvents, event);
+ break;
+ }
+ case TunerFilterEvent::ipPayload: {
+ getHidlIpPayloadEvent(filterEvents, event);
+ break;
+ }
+ case TunerFilterEvent::temi: {
+ getHidlTemiEvent(filterEvents, event);
+ break;
+ }
+ case TunerFilterEvent::monitor: {
+ getHidlMonitorEvent(filterEvents, eventExt);
+ break;
+ }
+ case TunerFilterEvent::startId: {
+ getHidlRestartEvent(filterEvents, eventExt);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void TunerFilterCallback::getHidlMediaEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ hidl_handle handle = hidl_handle(makeFromAidl(filterEvents[i]
+ .get<TunerFilterEvent::media>().avMemory));
+ event.events.resize(i + 1);
+ event.events[i].media({
+ .avMemory = handle,
+ .streamId = static_cast<DemuxStreamId>(filterEvents[i]
+ .get<TunerFilterEvent::media>().streamId),
+ .isPtsPresent = filterEvents[i]
+ .get<TunerFilterEvent::media>().isPtsPresent,
+ .pts = static_cast<uint64_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().pts),
+ .dataLength = static_cast<uint32_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().dataLength),
+ .offset = static_cast<uint32_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().offset),
+ .isSecureMemory = filterEvents[i]
+ .get<TunerFilterEvent::media>().isSecureMemory,
+ .avDataId = static_cast<uint64_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().avDataId),
+ .mpuSequenceNumber = static_cast<uint32_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().offset),
+ .isPesPrivateData = filterEvents[i]
+ .get<TunerFilterEvent::media>().isPesPrivateData,
+ });
+
+ if (filterEvents[i].get<TunerFilterEvent::media>().isAudioExtraMetaData) {
+ event.events[i].media().extraMetaData.audio({
+ .adFade = static_cast<uint8_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().audio.adFade),
+ .adPan = static_cast<uint8_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().audio.adPan),
+ .versionTextTag = static_cast<uint8_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().audio.versionTextTag),
+ .adGainCenter = static_cast<uint8_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().audio.adGainCenter),
+ .adGainFront = static_cast<uint8_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().audio.adGainFront),
+ .adGainSurround = static_cast<uint8_t>(filterEvents[i]
+ .get<TunerFilterEvent::media>().audio.adGainSurround),
+ });
+ } else {
+ event.events[i].media().extraMetaData.noinit();
+ }
+ }
+}
+
+void TunerFilterCallback::getHidlSectionEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ auto section = filterEvents[i].get<TunerFilterEvent::section>();
+ event.events.resize(i + 1);
+ event.events[i].section({
+ .tableId = static_cast<uint16_t>(section.tableId),
+ .version = static_cast<uint16_t>(section.version),
+ .sectionNum = static_cast<uint16_t>(section.sectionNum),
+ .dataLength = static_cast<uint16_t>(section.dataLength),
+ });
+ }
+}
+
+void TunerFilterCallback::getHidlPesEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ auto pes = filterEvents[i].get<TunerFilterEvent::pes>();
+ event.events.resize(i + 1);
+ event.events[i].pes({
+ .streamId = static_cast<DemuxStreamId>(pes.streamId),
+ .dataLength = static_cast<uint16_t>(pes.dataLength),
+ .mpuSequenceNumber = static_cast<uint32_t>(pes.mpuSequenceNumber),
+ });
+ }
+}
+
+void TunerFilterCallback::getHidlTsRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ auto ts = filterEvents[i].get<TunerFilterEvent::tsRecord>();
+ event.events.resize(i + 1);
+ event.events[i].tsRecord({
+ .tsIndexMask = static_cast<uint32_t>(ts.tsIndexMask),
+ .byteNumber = static_cast<uint64_t>(ts.byteNumber),
+ });
+ event.events[i].tsRecord().pid.tPid(static_cast<DemuxTpid>(ts.pid));
+
+ switch (ts.scIndexMask.getTag()) {
+ case TunerFilterScIndexMask::sc: {
+ event.events[i].tsRecord().scIndexMask.sc(
+ ts.scIndexMask.get<TunerFilterScIndexMask::sc>());
+ break;
+ }
+ case TunerFilterScIndexMask::scHevc: {
+ event.events[i].tsRecord().scIndexMask.scHevc(
+ ts.scIndexMask.get<TunerFilterScIndexMask::scHevc>());
+ break;
+ }
+ default:
+ break;
+ }
+
+ eventExt.events.resize(i + 1);
+ if (ts.isExtended) {
+ eventExt.events[i].tsRecord({
+ .pts = static_cast<uint64_t>(ts.pts),
+ .firstMbInSlice = static_cast<uint32_t>(ts.firstMbInSlice),
+ });
+ } else {
+ eventExt.events[i].noinit();
+ }
+ }
+}
+
+void TunerFilterCallback::getHidlMmtpRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ auto mmtp = filterEvents[i].get<TunerFilterEvent::mmtpRecord>();
+ event.events.resize(i + 1);
+ event.events[i].mmtpRecord({
+ .scHevcIndexMask = static_cast<uint32_t>(mmtp.scHevcIndexMask),
+ .byteNumber = static_cast<uint64_t>(mmtp.byteNumber),
+ });
+
+ eventExt.events.resize(i + 1);
+ if (mmtp.isExtended) {
+ eventExt.events[i].mmtpRecord({
+ .pts = static_cast<uint64_t>(mmtp.pts),
+ .mpuSequenceNumber = static_cast<uint32_t>(mmtp.mpuSequenceNumber),
+ .firstMbInSlice = static_cast<uint32_t>(mmtp.firstMbInSlice),
+ .tsIndexMask = static_cast<uint32_t>(mmtp.tsIndexMask),
+ });
+ } else {
+ eventExt.events[i].noinit();
+ }
+ }
+}
+
+void TunerFilterCallback::getHidlDownloadEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ auto download = filterEvents[i].get<TunerFilterEvent::download>();
+ event.events.resize(i + 1);
+ event.events[i].download({
+ .itemId = static_cast<uint32_t>(download.itemId),
+ .mpuSequenceNumber = static_cast<uint32_t>(download.mpuSequenceNumber),
+ .itemFragmentIndex = static_cast<uint32_t>(download.itemFragmentIndex),
+ .lastItemFragmentIndex = static_cast<uint32_t>(download.lastItemFragmentIndex),
+ .dataLength = static_cast<uint16_t>(download.dataLength),
+ });
+ }
+}
+
+void TunerFilterCallback::getHidlIpPayloadEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ auto ip = filterEvents[i].get<TunerFilterEvent::ipPayload>();
+ event.events.resize(i + 1);
+ event.events[i].ipPayload({
+ .dataLength = static_cast<uint16_t>(ip.dataLength),
+ });
+ }
+}
+
+void TunerFilterCallback::getHidlTemiEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event) {
+ for (int i = 0; i < filterEvents.size(); i++) {
+ auto temi = filterEvents[i].get<TunerFilterEvent::temi>();
+ event.events.resize(i + 1);
+ event.events[i].temi({
+ .pts = static_cast<uint64_t>(temi.pts),
+ .descrTag = static_cast<uint8_t>(temi.descrTag),
+ });
+ vector<uint8_t> descrData(temi.descrData.size());
+ copy(temi.descrData.begin(), temi.descrData.end(), descrData.begin());
+ }
+}
+
+void TunerFilterCallback::getHidlMonitorEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEventExt& eventExt) {
+ auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
+ eventExt.events.resize(1);
+ switch (monitor.getTag()) {
+ case TunerFilterMonitorEvent::scramblingStatus: {
+ eventExt.events[0].monitorEvent().scramblingStatus(
+ static_cast<ScramblingStatus>(monitor.scramblingStatus));
+ break;
+ }
+ case TunerFilterMonitorEvent::cid: {
+ eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+ break;
+ }
+ default:
+ eventExt.events[0].noinit();
+ break;
+ }
+}
+
+void TunerFilterCallback::getHidlRestartEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEventExt& eventExt) {
+ uint32_t startId = filterEvents[0].get<TunerFilterEvent::startId>();
+ eventExt.events.resize(1);
+ eventExt.events[0].startId(static_cast<uint32_t>(startId));
}
Result FilterClient::getFilterMq() {
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index f5539e0..21919ac 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -33,8 +33,14 @@
using Status = ::ndk::ScopedAStatus;
using ::aidl::android::media::tv::tuner::BnTunerFilterCallback;
using ::aidl::android::media::tv::tuner::ITunerFilter;
+using ::aidl::android::media::tv::tuner::TunerDemuxIpAddress;
+using ::aidl::android::media::tv::tuner::TunerFilterAvSettings;
using ::aidl::android::media::tv::tuner::TunerFilterConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterDownloadSettings;
using ::aidl::android::media::tv::tuner::TunerFilterEvent;
+using ::aidl::android::media::tv::tuner::TunerFilterPesDataSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterRecordSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionSettings;
using ::aidl::android::media::tv::tuner::TunerFilterSettings;
using ::android::hardware::EventFlag;
@@ -43,8 +49,19 @@
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::hardware::hidl_handle;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpAddress;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
using ::android::hardware::tv::tuner::V1_0::IFilter;
using ::android::hardware::tv::tuner::V1_0::Result;
using ::android::hardware::tv::tuner::V1_1::AvStreamType;
@@ -71,6 +88,26 @@
private:
void getHidlFilterEvent(const vector<TunerFilterEvent>& filterEvents,
DemuxFilterEvent& event, DemuxFilterEventExt& eventExt);
+ void getHidlMediaEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+ void getHidlSectionEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+ void getHidlPesEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+ void getHidlTsRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event, DemuxFilterEventExt& eventExt);
+ void getHidlMmtpRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+ DemuxFilterEvent& event, DemuxFilterEventExt& eventExt);
+ void getHidlDownloadEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+ void getHidlIpPayloadEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+ void getHidlTemiEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+ void getHidlMonitorEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEventExt& eventExt);
+ void getHidlRestartEvent(
+ const vector<TunerFilterEvent>& filterEvents, DemuxFilterEventExt& eventExt);
sp<FilterClientCallback> mFilterClientCallback;
};
@@ -181,6 +218,21 @@
private:
TunerFilterConfiguration getAidlFilterSettings(DemuxFilterSettings configure);
+
+ TunerFilterConfiguration getAidlTsSettings(DemuxTsFilterSettings configure);
+ TunerFilterConfiguration getAidlMmtpSettings(DemuxMmtpFilterSettings mmtp);
+ TunerFilterConfiguration getAidlIpSettings(DemuxIpFilterSettings ip);
+ TunerFilterConfiguration getAidlTlvSettings(DemuxTlvFilterSettings tlv);
+ TunerFilterConfiguration getAidlAlpSettings(DemuxAlpFilterSettings alp);
+
+ TunerFilterAvSettings getAidlAvSettings(DemuxFilterAvSettings hidlAv);
+ TunerFilterSectionSettings getAidlSectionSettings(DemuxFilterSectionSettings hidlSection);
+ TunerFilterPesDataSettings getAidlPesDataSettings(DemuxFilterPesDataSettings hidlPesData);
+ TunerFilterRecordSettings getAidlRecordSettings(DemuxFilterRecordSettings hidlRecord);
+ TunerFilterDownloadSettings getAidlDownloadSettings(DemuxFilterDownloadSettings hidlDownload);
+
+ void getAidlIpAddress(DemuxIpAddress ipAddr,
+ TunerDemuxIpAddress& srcIpAddress, TunerDemuxIpAddress& dstIpAddress);
Result getFilterMq();
int copyData(uint8_t* buffer, int size);
void checkIsMediaFilter(DemuxFilterType type);
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 0540aac..08573a6 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -21,8 +21,8 @@
#include "FrontendClient.h"
-using ::aidl::android::media::tv::tuner::TunerFrontendDvbtSettings;
using ::aidl::android::media::tv::tuner::TunerFrontendScanAtsc3PlpInfo;
+using ::aidl::android::media::tv::tuner::TunerFrontendUnionSettings;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogType;
@@ -43,6 +43,7 @@
using ::android::hardware::tv::tuner::V1_1::FrontendDtmbModulation;
using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
namespace android {
@@ -86,14 +87,13 @@
Result FrontendClient::tune(const FrontendSettings& settings,
const FrontendSettingsExt1_1& settingsExt1_1) {
if (mTunerFrontend != NULL) {
- // TODO: aidl frontend settings to include Tuner HAL 1.1 settings
TunerFrontendSettings tunerFeSettings = getAidlFrontendSettings(settings, settingsExt1_1);
Status s = mTunerFrontend->tune(tunerFeSettings);
return ClientHelper::getServiceSpecificErrorCode(s);
}
Result result;
- if (mFrontend_1_1 != NULL) {
+ if (mFrontend_1_1 != NULL && validateExtendedSettings(settingsExt1_1)) {
result = mFrontend_1_1->tune_1_1(settings, settingsExt1_1);
return result;
}
@@ -123,14 +123,13 @@
Result FrontendClient::scan(const FrontendSettings& settings, FrontendScanType type,
const FrontendSettingsExt1_1& settingsExt1_1) {
if (mTunerFrontend != NULL) {
- // TODO: aidl frontend settings to include Tuner HAL 1.1 settings
TunerFrontendSettings tunerFeSettings = getAidlFrontendSettings(settings, settingsExt1_1);
Status s = mTunerFrontend->scan(tunerFeSettings, (int)type);
return ClientHelper::getServiceSpecificErrorCode(s);
}
Result result;
- if (mFrontend_1_1 != NULL) {
+ if (mFrontend_1_1 != NULL && validateExtendedSettings(settingsExt1_1)) {
result = mFrontend_1_1->scan_1_1(settings, type, settingsExt1_1);
return result;
}
@@ -293,6 +292,8 @@
return Result::INVALID_STATE;
}
+/////////////// TunerFrontend Helper Methods ///////////////////////
+
shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() {
return mTunerFrontend;
}
@@ -302,52 +303,59 @@
}
TunerFrontendSettings FrontendClient::getAidlFrontendSettings(const FrontendSettings& settings,
- const FrontendSettingsExt1_1& /*settingsExt1_1*/) {
- // TODO: complete hidl to aidl frontend settings conversion
- TunerFrontendSettings s;
+ const FrontendSettingsExt1_1& settingsExt1_1) {
+ bool isExtended = validateExtendedSettings(settingsExt1_1);
+ TunerFrontendSettings s{
+ .isExtended = isExtended,
+ .endFrequency = (int) settingsExt1_1.endFrequency,
+ .inversion = (int) settingsExt1_1.inversion,
+ };
+
+ if (settingsExt1_1.settingExt.getDiscriminator()
+ == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dtmb) {
+ s.settings.set<TunerFrontendUnionSettings::dtmb>(getAidlDtmbSettings(settingsExt1_1));
+ return s;
+ }
+
switch (settings.getDiscriminator()) {
case FrontendSettings::hidl_discriminator::analog: {
+ s.settings.set<TunerFrontendUnionSettings::analog>(
+ getAidlAnalogSettings(settings, settingsExt1_1));
break;
}
case FrontendSettings::hidl_discriminator::atsc: {
+ s.settings.set<TunerFrontendUnionSettings::atsc>(getAidlAtscSettings(settings));
break;
}
case FrontendSettings::hidl_discriminator::atsc3: {
+ s.settings.set<TunerFrontendUnionSettings::atsc3>(getAidlAtsc3Settings(settings));
break;
}
case FrontendSettings::hidl_discriminator::dvbs: {
+ s.settings.set<TunerFrontendUnionSettings::dvbs>(
+ getAidlDvbsSettings(settings, settingsExt1_1));
break;
}
case FrontendSettings::hidl_discriminator::dvbc: {
+ s.settings.set<TunerFrontendUnionSettings::cable>(
+ getAidlCableSettings(settings, settingsExt1_1));
break;
}
case FrontendSettings::hidl_discriminator::dvbt: {
- TunerFrontendDvbtSettings dvbtSettings{
- .frequency = (int)settings.dvbt().frequency,
- .transmissionMode = (int)settings.dvbt().transmissionMode,
- .bandwidth = (int)settings.dvbt().bandwidth,
- .constellation = (int)settings.dvbt().constellation,
- .hierarchy = (int)settings.dvbt().hierarchy,
- .hpCodeRate = (int)settings.dvbt().hpCoderate,
- .lpCodeRate = (int)settings.dvbt().lpCoderate,
- .guardInterval = (int)settings.dvbt().guardInterval,
- .isHighPriority = settings.dvbt().isHighPriority,
- .standard = (int)settings.dvbt().standard,
- .isMiso = settings.dvbt().isMiso,
- .plpMode = (int)settings.dvbt().plpMode,
- .plpId = (int)settings.dvbt().plpId,
- .plpGroupId = (int)settings.dvbt().plpGroupId,
- };
- s.set<TunerFrontendSettings::dvbt>(dvbtSettings);
+ s.settings.set<TunerFrontendUnionSettings::dvbt>(
+ getAidlDvbtSettings(settings, settingsExt1_1));
break;
}
case FrontendSettings::hidl_discriminator::isdbs: {
+ s.settings.set<TunerFrontendUnionSettings::isdbs>(getAidlIsdbsSettings(settings));
break;
}
case FrontendSettings::hidl_discriminator::isdbs3: {
+ s.settings.set<TunerFrontendUnionSettings::isdbs3>(getAidlIsdbs3Settings(settings));
break;
}
case FrontendSettings::hidl_discriminator::isdbt: {
+ s.settings.set<TunerFrontendUnionSettings::isdbt>(getAidlIsdbtSettings(settings));
break;
}
default:
@@ -356,6 +364,192 @@
return s;
}
+TunerFrontendAnalogSettings FrontendClient::getAidlAnalogSettings(const FrontendSettings& settings,
+ const FrontendSettingsExt1_1& settingsExt1_1) {
+ TunerFrontendAnalogSettings analogSettings{
+ .frequency = (int)settings.analog().frequency,
+ .signalType = (int)settings.analog().type,
+ .sifStandard = (int)settings.analog().sifStandard,
+ };
+ if (settingsExt1_1.settingExt.getDiscriminator()
+ == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::analog) {
+ analogSettings.isExtended = true;
+ analogSettings.aftFlag = (int)settingsExt1_1.settingExt.analog().aftFlag;
+ } else {
+ analogSettings.isExtended = false;
+ }
+ return analogSettings;
+}
+
+TunerFrontendDvbsSettings FrontendClient::getAidlDvbsSettings(const FrontendSettings& settings,
+ const FrontendSettingsExt1_1& settingsExt1_1) {
+ TunerFrontendDvbsSettings dvbsSettings{
+ .frequency = (int)settings.dvbs().frequency,
+ .modulation = (int)settings.dvbs().modulation,
+ .codeRate = {
+ .fec = (long)settings.dvbs().coderate.fec,
+ .isLinear = settings.dvbs().coderate.isLinear,
+ .isShortFrames = settings.dvbs().coderate.isShortFrames,
+ .bitsPer1000Symbol = (int)settings.dvbs().coderate.bitsPer1000Symbol,
+ },
+ .symbolRate = (int)settings.dvbs().symbolRate,
+ .rolloff = (int)settings.dvbs().rolloff,
+ .pilot = (int)settings.dvbs().pilot,
+ .inputStreamId = (int)settings.dvbs().inputStreamId,
+ .standard = (int)settings.dvbs().standard,
+ .vcm = (int)settings.dvbs().vcmMode,
+ };
+ if (settingsExt1_1.settingExt.getDiscriminator()
+ == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dvbs) {
+ dvbsSettings.isExtended = true;
+ dvbsSettings.scanType = (int)settingsExt1_1.settingExt.dvbs().scanType;
+ dvbsSettings.isDiseqcRxMessage = settingsExt1_1.settingExt.dvbs().isDiseqcRxMessage;
+ } else {
+ dvbsSettings.isExtended = false;
+ }
+ return dvbsSettings;
+}
+
+TunerFrontendCableSettings FrontendClient::getAidlCableSettings(const FrontendSettings& settings,
+ const FrontendSettingsExt1_1& settingsExt1_1) {
+ TunerFrontendCableSettings cableSettings{
+ .frequency = (int)settings.dvbc().frequency,
+ .modulation = (int)settings.dvbc().modulation,
+ .innerFec = (long)settings.dvbc().fec,
+ .symbolRate = (int)settings.dvbc().symbolRate,
+ .outerFec = (int)settings.dvbc().outerFec,
+ .annex = (int)settings.dvbc().annex,
+ .spectralInversion = (int)settings.dvbc().spectralInversion,
+ };
+ if (settingsExt1_1.settingExt.getDiscriminator()
+ == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dvbc) {
+ cableSettings.isExtended = true;
+ cableSettings.interleaveMode = (int)settingsExt1_1.settingExt.dvbc().interleaveMode;
+ cableSettings.bandwidth = (int)settingsExt1_1.settingExt.dvbc().bandwidth;
+ } else {
+ cableSettings.isExtended = false;
+ }
+ return cableSettings;
+}
+
+TunerFrontendDvbtSettings FrontendClient::getAidlDvbtSettings(const FrontendSettings& settings,
+ const FrontendSettingsExt1_1& settingsExt1_1) {
+ TunerFrontendDvbtSettings dvbtSettings{
+ .frequency = (int)settings.dvbt().frequency,
+ .transmissionMode = (int)settings.dvbt().transmissionMode,
+ .bandwidth = (int)settings.dvbt().bandwidth,
+ .constellation = (int)settings.dvbt().constellation,
+ .hierarchy = (int)settings.dvbt().hierarchy,
+ .hpCodeRate = (int)settings.dvbt().hpCoderate,
+ .lpCodeRate = (int)settings.dvbt().lpCoderate,
+ .guardInterval = (int)settings.dvbt().guardInterval,
+ .isHighPriority = settings.dvbt().isHighPriority,
+ .standard = (int)settings.dvbt().standard,
+ .isMiso = settings.dvbt().isMiso,
+ .plpMode = (int)settings.dvbt().plpMode,
+ .plpId = (int)settings.dvbt().plpId,
+ .plpGroupId = (int)settings.dvbt().plpGroupId,
+ };
+ if (settingsExt1_1.settingExt.getDiscriminator()
+ == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dvbt) {
+ dvbtSettings.isExtended = true;
+ dvbtSettings.constellation = (int)settingsExt1_1.settingExt.dvbt().constellation;
+ dvbtSettings.transmissionMode =
+ (int)settingsExt1_1.settingExt.dvbt().transmissionMode;
+ } else {
+ dvbtSettings.isExtended = false;
+ }
+ return dvbtSettings;
+}
+
+TunerFrontendDtmbSettings FrontendClient::getAidlDtmbSettings(
+ const FrontendSettingsExt1_1& settingsExt1_1) {
+ TunerFrontendDtmbSettings dtmbSettings{
+ .frequency = (int)settingsExt1_1.settingExt.dtmb().frequency,
+ .transmissionMode = (int)settingsExt1_1.settingExt.dtmb().transmissionMode,
+ .bandwidth = (int)settingsExt1_1.settingExt.dtmb().bandwidth,
+ .modulation = (int)settingsExt1_1.settingExt.dtmb().modulation,
+ .codeRate = (int)settingsExt1_1.settingExt.dtmb().codeRate,
+ .guardInterval = (int)settingsExt1_1.settingExt.dtmb().guardInterval,
+ .interleaveMode = (int)settingsExt1_1.settingExt.dtmb().interleaveMode,
+ };
+ return dtmbSettings;
+}
+
+TunerFrontendAtscSettings FrontendClient::getAidlAtscSettings(const FrontendSettings& settings) {
+ TunerFrontendAtscSettings atscSettings{
+ .frequency = (int)settings.atsc().frequency,
+ .modulation = (int)settings.atsc().modulation,
+ };
+ return atscSettings;
+}
+
+TunerFrontendAtsc3Settings FrontendClient::getAidlAtsc3Settings(const FrontendSettings& settings) {
+ TunerFrontendAtsc3Settings atsc3Settings{
+ .frequency = (int)settings.atsc3().frequency,
+ .bandwidth = (int)settings.atsc3().bandwidth,
+ .demodOutputFormat = (int)settings.atsc3().demodOutputFormat,
+ };
+ atsc3Settings.plpSettings.resize(settings.atsc3().plpSettings.size());
+ for (auto plpSetting : settings.atsc3().plpSettings) {
+ atsc3Settings.plpSettings.push_back({
+ .plpId = (int)plpSetting.plpId,
+ .modulation = (int)plpSetting.modulation,
+ .interleaveMode = (int)plpSetting.interleaveMode,
+ .codeRate = (int)plpSetting.codeRate,
+ .fec = (int)plpSetting.fec,
+ });
+ }
+ return atsc3Settings;
+}
+
+TunerFrontendIsdbsSettings FrontendClient::getAidlIsdbsSettings(const FrontendSettings& settings) {
+ TunerFrontendIsdbsSettings isdbsSettings{
+ .frequency = (int)settings.isdbs().frequency,
+ .streamId = (int)settings.isdbs().streamId,
+ .streamIdType = (int)settings.isdbs().streamIdType,
+ .modulation = (int)settings.isdbs().modulation,
+ .codeRate = (int)settings.isdbs().coderate,
+ .symbolRate = (int)settings.isdbs().symbolRate,
+ .rolloff = (int)settings.isdbs().rolloff,
+ };
+ return isdbsSettings;
+}
+
+TunerFrontendIsdbs3Settings FrontendClient::getAidlIsdbs3Settings(
+ const FrontendSettings& settings) {
+ TunerFrontendIsdbs3Settings isdbs3Settings{
+ .frequency = (int)settings.isdbs3().frequency,
+ .streamId = (int)settings.isdbs3().streamId,
+ .streamIdType = (int)settings.isdbs3().streamIdType,
+ .modulation = (int)settings.isdbs3().modulation,
+ .codeRate = (int)settings.isdbs3().coderate,
+ .symbolRate = (int)settings.isdbs3().symbolRate,
+ .rolloff = (int)settings.isdbs3().rolloff,
+ };
+ return isdbs3Settings;
+}
+
+TunerFrontendIsdbtSettings FrontendClient::getAidlIsdbtSettings(const FrontendSettings& settings) {
+ TunerFrontendIsdbtSettings isdbtSettings{
+ .frequency = (int)settings.isdbt().frequency,
+ .modulation = (int)settings.isdbt().modulation,
+ .bandwidth = (int)settings.isdbt().bandwidth,
+ .mode = (int)settings.isdbt().mode,
+ .codeRate = (int)settings.isdbt().coderate,
+ .guardInterval = (int)settings.isdbt().guardInterval,
+ .serviceAreaId = (int)settings.isdbt().serviceAreaId,
+ };
+ return isdbtSettings;
+}
+
+bool FrontendClient::validateExtendedSettings(const FrontendSettingsExt1_1& settingsExt1_1) {
+ return settingsExt1_1.endFrequency != (uint32_t)Constant::INVALID_FRONTEND_SETTING_FREQUENCY
+ || settingsExt1_1.inversion != FrontendSpectralInversion::UNDEFINED
+ || settingsExt1_1.settingExt.getDiscriminator()
+ != FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::noinit;
+}
+
/////////////// TunerFrontendCallback ///////////////////////
TunerFrontendCallback::TunerFrontendCallback(sp<FrontendClientCallback> frontendClientCallback)
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 17fd583..b0107ff 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -31,6 +31,16 @@
using ::aidl::android::media::tv::tuner::BnTunerFrontendCallback;
using ::aidl::android::media::tv::tuner::ITunerFrontend;
+using ::aidl::android::media::tv::tuner::TunerFrontendAnalogSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendAtscSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendAtsc3Settings;
+using ::aidl::android::media::tv::tuner::TunerFrontendCableSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendDvbsSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendDvbtSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendDtmbSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendIsdbsSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendIsdbs3Settings;
+using ::aidl::android::media::tv::tuner::TunerFrontendIsdbtSettings;
using ::aidl::android::media::tv::tuner::TunerFrontendScanMessage;
using ::aidl::android::media::tv::tuner::TunerFrontendSettings;
@@ -172,8 +182,24 @@
int getId();
private:
- TunerFrontendSettings getAidlFrontendSettings(const FrontendSettings& settings,
- const FrontendSettingsExt1_1& settingsExt1_1);
+ TunerFrontendSettings getAidlFrontendSettings(
+ const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+ TunerFrontendAnalogSettings getAidlAnalogSettings(
+ const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+ TunerFrontendDvbsSettings getAidlDvbsSettings(
+ const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+ TunerFrontendCableSettings getAidlCableSettings(
+ const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+ TunerFrontendDvbtSettings getAidlDvbtSettings(
+ const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+ TunerFrontendDtmbSettings getAidlDtmbSettings(const FrontendSettingsExt1_1& settingsExt1_1);
+ TunerFrontendAtscSettings getAidlAtscSettings(const FrontendSettings& settings);
+ TunerFrontendAtsc3Settings getAidlAtsc3Settings(const FrontendSettings& settings);
+ TunerFrontendIsdbsSettings getAidlIsdbsSettings(const FrontendSettings& settings);
+ TunerFrontendIsdbs3Settings getAidlIsdbs3Settings(const FrontendSettings& settings);
+ TunerFrontendIsdbtSettings getAidlIsdbtSettings(const FrontendSettings& settings);
+
+ bool validateExtendedSettings(const FrontendSettingsExt1_1& settingsExt1_1);
/**
* An AIDL Tuner Frontend Singleton assigned at the first time when the Tuner Client
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 1286fc1..4b0062b 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -256,7 +256,7 @@
CountDownLatch latch = new CountDownLatch(1);
- addManagerCallback(new MediaRouter2Manager.Callback());
+ addManagerCallback(new MediaRouter2Manager.Callback() {});
addRouterCallback(new MediaRouter2.RouteCallback() {});
addTransferCallback(new MediaRouter2.TransferCallback() {
@Override
@@ -530,7 +530,7 @@
@Test
public void testSetSystemRouteVolume() throws Exception {
// ensure client
- addManagerCallback(new MediaRouter2Manager.Callback());
+ addManagerCallback(new MediaRouter2Manager.Callback() {});
String selectedSystemRouteId =
MediaRouter2Utils.getOriginalId(
mManager.getActiveSessions().get(0).getSelectedRoutes().get(0));
@@ -902,7 +902,7 @@
private void releaseAllSessions() {
// ensure ManagerRecord in MediaRouter2ServiceImpl
- addManagerCallback(new MediaRouter2Manager.Callback());
+ addManagerCallback(new MediaRouter2Manager.Callback() {});
for (RoutingSessionInfo session : mManager.getActiveSessions()) {
mManager.releaseSession(session);
diff --git a/native/android/OWNERS b/native/android/OWNERS
index ac5a895..d414ed4 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -1,3 +1,4 @@
per-file libandroid_net.map.txt, net.c = set noparent
per-file libandroid_net.map.txt, net.c = codewiz@google.com, jchalard@google.com, junyulai@google.com
per-file libandroid_net.map.txt, net.c = lorenzo@google.com, reminv@google.com, satk@google.com
+per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index d464587..3d633ea 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -27,14 +27,13 @@
],
shared_libs: [
- "libandroid_runtime",
"libhwui",
"liblog",
],
header_libs: [
- "libhwui_internal_headers",
"jni_headers",
+ "libhwui_internal_headers",
],
static_libs: ["libarect"],
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index 620c7ae..72589e3 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -18,6 +18,7 @@
import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
import static android.text.TextUtils.emptyIfNull;
+import static android.text.TextUtils.isEmpty;
import static android.text.TextUtils.withoutPrefix;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -67,7 +68,13 @@
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
String deviceProfile = getRequest().getDeviceProfile();
- String profileName = getDeviceProfileName(deviceProfile);
+ String profilePrivacyDisclaimer = emptyIfNull(getRequest()
+ .getDeviceProfilePrivilegesDescription())
+ .replace("APP_NAME", getCallingAppName());
+ boolean useDeviceProfile = deviceProfile != null && !isEmpty(profilePrivacyDisclaimer);
+ String profileName = useDeviceProfile
+ ? getDeviceProfileName(deviceProfile)
+ : getString(R.string.profile_name_generic);
if (getRequest().isSingleDevice()) {
setContentView(R.layout.device_confirmation);
@@ -110,15 +117,12 @@
TextView profileSummary = findViewById(R.id.profile_summary);
- if (deviceProfile != null) {
- String privacyDisclaimer = emptyIfNull(getRequest()
- .getDeviceProfilePrivilegesDescription())
- .replace("APP_NAME", getCallingAppName());
+ if (useDeviceProfile) {
profileSummary.setVisibility(View.VISIBLE);
profileSummary.setText(getString(R.string.profile_summary,
getCallingAppName(),
profileName,
- privacyDisclaimer));
+ profilePrivacyDisclaimer));
} else {
profileSummary.setVisibility(View.GONE);
}
@@ -142,7 +146,7 @@
return getString(R.string.profile_name_watch);
}
default: {
- Log.wtf(LOG_TAG,
+ Log.w(LOG_TAG,
"No localized profile name found for device profile: " + deviceProfile);
return withoutPrefix("android.app.role.COMPANION_DEVICE_", deviceProfile)
.toLowerCase()
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
new file mode 100644
index 0000000..9afa5d1
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for all core connectivity services.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializer {
+ private ConnectivityFrameworkInitializer() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all core
+ * connectivity services to {@link Context}, so that {@link Context#getSystemService} can
+ * return them.
+ *
+ * @throws IllegalStateException if this is called anywhere besides
+ * {@link SystemServiceRegistry}.
+ */
+ public static void registerServiceWrappers() {
+ // registerContextAwareService will throw if this is called outside of SystemServiceRegistry
+ // initialization.
+ SystemServiceRegistry.registerContextAwareService(
+ Context.CONNECTIVITY_SERVICE,
+ ConnectivityManager.class,
+ (context, serviceBinder) -> {
+ IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder);
+ return new ConnectivityManager(context, icm);
+ }
+ );
+
+ // TODO: move outside of the connectivity JAR
+ SystemServiceRegistry.registerContextAwareService(
+ Context.VPN_MANAGEMENT_SERVICE,
+ VpnManager.class,
+ (context) -> {
+ final ConnectivityManager cm = context.getSystemService(
+ ConnectivityManager.class);
+ return cm.createVpnManager();
+ }
+ );
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+ ConnectivityDiagnosticsManager.class,
+ (context) -> {
+ final ConnectivityManager cm = context.getSystemService(
+ ConnectivityManager.class);
+ return cm.createDiagnosticsManager();
+ }
+ );
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.TEST_NETWORK_SERVICE,
+ TestNetworkManager.class,
+ context -> {
+ final ConnectivityManager cm = context.getSystemService(
+ ConnectivityManager.class);
+ return cm.startOrGetTestNetworkManager();
+ }
+ );
+ }
+}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index ac8f9c9..2716e09 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -4609,7 +4609,7 @@
// Set HTTP proxy system properties to match network.
// TODO: Deprecate this static method and replace it with a non-static version.
try {
- Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+ Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy());
} catch (SecurityException e) {
// The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy.
Log.e(TAG, "Can't set proxy properties", e);
@@ -4823,6 +4823,28 @@
}
}
+ /** @hide */
+ public TestNetworkManager startOrGetTestNetworkManager() {
+ final IBinder tnBinder;
+ try {
+ tnBinder = mService.startOrGetTestNetworkService();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder));
+ }
+
+ /** @hide */
+ public VpnManager createVpnManager() {
+ return new VpnManager(mContext, mService);
+ }
+
+ /** @hide */
+ public ConnectivityDiagnosticsManager createDiagnosticsManager() {
+ return new ConnectivityDiagnosticsManager(mContext, mService);
+ }
+
/**
* Simulates a Data Stall for the specified Network.
*
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index d22d82d..27aa15d 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -20,6 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -775,7 +776,8 @@
* @param underlyingNetworks the new list of underlying networks.
* @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])}
*/
- public final void setUnderlyingNetworks(@Nullable List<Network> underlyingNetworks) {
+ public final void setUnderlyingNetworks(
+ @SuppressLint("NullableCollection") @Nullable List<Network> underlyingNetworks) {
final ArrayList<Network> underlyingArray = (underlyingNetworks != null)
? new ArrayList<>(underlyingNetworks) : null;
queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray));
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 3843b9a..55b2c3c 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -34,9 +34,9 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
+import com.android.net.module.util.CollectionUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -401,11 +401,18 @@
public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27;
/**
- * Indicates that this network is not managed by a Virtual Carrier Network (VCN).
- *
- * TODO(b/177299683): Add additional clarifying javadoc.
+ * Indicates that this network is not subsumed by a Virtual Carrier Network (VCN).
+ * <p>
+ * To provide an experience on a VCN similar to a single traditional carrier network, in
+ * some cases the system sets this bit is set by default in application's network requests,
+ * and may choose to remove it at its own discretion when matching the request to a network.
+ * <p>
+ * Applications that want to know about a Virtual Carrier Network's underlying networks,
+ * for example to use them for multipath purposes, should remove this bit from their network
+ * requests ; the system will not add it back once removed.
* @hide
*/
+ @SystemApi
public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
@@ -767,7 +774,7 @@
if (originalOwnerUid == creatorUid) {
setOwnerUid(creatorUid);
}
- if (ArrayUtils.contains(originalAdministratorUids, creatorUid)) {
+ if (CollectionUtils.contains(originalAdministratorUids, creatorUid)) {
setAdministratorUids(new int[] {creatorUid});
}
// There is no need to clear the UIDs, they have already been cleared by clearAll() above.
@@ -1779,6 +1786,15 @@
return 0;
}
+ private <T extends Parcelable> void writeParcelableArraySet(Parcel in,
+ @Nullable ArraySet<T> val, int flags) {
+ final int size = (val != null) ? val.size() : -1;
+ in.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ in.writeParcelable(val.valueAt(i), flags);
+ }
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mNetworkCapabilities);
@@ -1789,7 +1805,7 @@
dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
dest.writeParcelable((Parcelable) mTransportInfo, flags);
dest.writeInt(mSignalStrength);
- dest.writeArraySet(mUids);
+ writeParcelableArraySet(dest, mUids, flags);
dest.writeString(mSSID);
dest.writeBoolean(mPrivateDnsBroken);
dest.writeIntArray(getAdministratorUids());
@@ -1812,8 +1828,7 @@
netCap.mNetworkSpecifier = in.readParcelable(null);
netCap.mTransportInfo = in.readParcelable(null);
netCap.mSignalStrength = in.readInt();
- netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
- null /* ClassLoader, null for default */);
+ netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */);
netCap.mSSID = in.readString();
netCap.mPrivateDnsBroken = in.readBoolean();
netCap.setAdministratorUids(in.createIntArray());
@@ -1826,6 +1841,20 @@
public NetworkCapabilities[] newArray(int size) {
return new NetworkCapabilities[size];
}
+
+ private @Nullable <T extends Parcelable> ArraySet<T> readParcelableArraySet(Parcel in,
+ @Nullable ClassLoader loader) {
+ final int size = in.readInt();
+ if (size < 0) {
+ return null;
+ }
+ final ArraySet<T> result = new ArraySet<>(size);
+ for (int i = 0; i < size; i++) {
+ final T value = in.readParcelable(loader);
+ result.append(value);
+ }
+ return result;
+ }
};
@Override
@@ -1873,7 +1902,7 @@
sb.append(" OwnerUid: ").append(mOwnerUid);
}
- if (!ArrayUtils.isEmpty(mAdministratorUids)) {
+ if (mAdministratorUids != null && mAdministratorUids.length != 0) {
sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids));
}
@@ -2506,7 +2535,7 @@
@NonNull
public NetworkCapabilities build() {
if (mCaps.getOwnerUid() != Process.INVALID_UID) {
- if (!ArrayUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
+ if (!CollectionUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
throw new IllegalStateException("The owner UID must be included in "
+ " administrator UIDs.");
}
diff --git a/packages/Connectivity/framework/src/android/net/Proxy.java b/packages/Connectivity/framework/src/android/net/Proxy.java
index 03b07e0..03cfbbb 100644
--- a/packages/Connectivity/framework/src/android/net/Proxy.java
+++ b/packages/Connectivity/framework/src/android/net/Proxy.java
@@ -16,8 +16,10 @@
package android.net;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
@@ -245,7 +247,19 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final void setHttpProxySystemProperty(ProxyInfo p) {
+ @Deprecated
+ public static void setHttpProxySystemProperty(ProxyInfo p) {
+ setHttpProxyConfiguration(p);
+ }
+
+ /**
+ * Set HTTP proxy configuration for the process to match the provided ProxyInfo.
+ *
+ * If the provided ProxyInfo is null, the proxy configuration will be cleared.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static void setHttpProxyConfiguration(@Nullable ProxyInfo p) {
String host = null;
String port = null;
String exclList = null;
@@ -256,11 +270,11 @@
exclList = ProxyUtils.exclusionListAsString(p.getExclusionList());
pacFileUrl = p.getPacFileUrl();
}
- setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+ setHttpProxyConfiguration(host, port, exclList, pacFileUrl);
}
/** @hide */
- public static final void setHttpProxySystemProperty(String host, String port, String exclList,
+ public static void setHttpProxyConfiguration(String host, String port, String exclList,
Uri pacFileUrl) {
if (exclList != null) exclList = exclList.replace(",", "|");
if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList);
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index c8f3bd3..8fc3181 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -57,6 +57,7 @@
static_libs: [
"net-utils-device-common",
"net-utils-framework-common",
+ "netd-client",
],
apex_available: [
"//apex_available:platform",
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index d472311..7cc5994 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -26,7 +26,6 @@
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationRequest;
-import android.location.LocationResult;
import android.location.provider.ILocationProvider;
import android.location.provider.ILocationProviderManager;
import android.location.provider.ProviderProperties;
@@ -49,6 +48,7 @@
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.List;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -167,10 +167,13 @@
}
@Override
- public void onReportLocation(LocationResult locationResult) {
- for (int i = 0; i < locationResult.size(); i++) {
- mLocations.add(locationResult.get(i));
- }
+ public void onReportLocation(Location location) {
+ mLocations.add(location);
+ }
+
+ @Override
+ public void onReportLocations(List<Location> locations) {
+ mLocations.addAll(locations);
}
@Override
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index 65e75cd..d411831 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -83,9 +83,9 @@
<string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak tableta honetan."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telebista honetan."</string>
<string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telefono honetan."</string>
- <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telefonoak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
- <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik tabletak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
- <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Telebistak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telebistak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+ <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartuko duzu zeu zarela hura erabiltzeagatik telefonoak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+ <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartuko duzu zeu zarela hura erabiltzeagatik tabletak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+ <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Telebistak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartuko duzu zeu zarela hura erabiltzeagatik telebistak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
<string name="anonymous_source_continue" msgid="4375745439457209366">"Egin aurrera"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"Ezarpenak"</string>
<string name="wear_app_channel" msgid="1960809674709107850">"Wear aplikazioak instalatzea/desinstalatzea"</string>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
index 8a73fb5..2be00b9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
@@ -15,11 +15,17 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="4dp"
+ android:left="4dp"
+ android:right="4dp"
+ android:bottom="4dp">
- <size android:height="24dp" android:width="24dp" />
- <solid android:color="@color/thumb_off" />
- <stroke android:width="4dp" android:color="@android:color/transparent" />
+ <shape android:shape="oval" >
+ <size android:height="20dp" android:width="20dp" />
+ <solid android:color="@color/thumb_off" />
+ </shape>
-</shape>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
index 8a0af00..e85eb42 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
@@ -15,11 +15,17 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="4dp"
+ android:left="4dp"
+ android:right="4dp"
+ android:bottom="4dp">
- <size android:height="24dp" android:width="24dp" />
- <solid android:color="?android:attr/colorAccent" />
- <stroke android:width="4dp" android:color="@android:color/transparent" />
+ <shape android:shape="oval" >
+ <size android:height="20dp" android:width="20dp" />
+ <solid android:color="?android:attr/colorAccent" />
+ </shape>
-</shape>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
index 1be3a8e..b29f459 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
@@ -20,7 +20,7 @@
<item
android:width="13.33dp"
android:height="1.67dp"
- android:left="19dp"
+ android:left="21dp"
android:gravity="center"
android:drawable="@drawable/track_off_indicator" />
</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
index 3cc490f..c838654 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
@@ -17,17 +17,17 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="24dp"
- android:viewportWidth="48"
- android:viewportHeight="24">
+ android:width="52dp"
+ android:height="28dp"
+ android:viewportWidth="52"
+ android:viewportHeight="28">
<group>
<clip-path
- android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+ android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
<path
- android:pathData="M0 0V24H48V0"
+ android:pathData="M0 0V28H52V0"
android:fillColor="@color/track_off" />
</group>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
index 2553891..cf24112 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
@@ -21,6 +21,6 @@
android:width="13.19dp"
android:height="10.06dp"
android:gravity="center"
- android:right="19dp"
+ android:right="21dp"
android:drawable="@drawable/track_on_indicator" />
</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
index 68ce19b..bb1a7ef 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
@@ -17,19 +17,20 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="24dp"
- android:viewportWidth="48"
- android:viewportHeight="24"
+ android:width="52dp"
+ android:height="28dp"
+ android:viewportWidth="52"
+ android:viewportHeight="28"
android:tint="@*android:color/switch_track_material">
<group>
<clip-path
- android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+ android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
<path
- android:pathData="M0 0V24H48V0"
+ android:pathData="M0 0V28H52V0"
android:fillColor="@*android:color/white_disabled_material" />
+
</group>
</vector>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
index 7e3ce9d..52779bc 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
@@ -26,7 +26,7 @@
android:minHeight="@dimen/min_switch_bar_height"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:background="?android:attr/colorBackground"
+ android:background="?android:attr/selectableItemBackground"
android:paddingLeft="@dimen/switchbar_margin_start"
android:paddingRight="@dimen/switchbar_margin_end">
@@ -35,7 +35,7 @@
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
- android:paddingRight="54dp"
+ android:layout_marginRight="16dp"
android:layout_gravity="center_vertical"
android:maxLines="2"
android:ellipsize="end"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
index 9dc0af3..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
@@ -17,7 +17,6 @@
<resources>
- <color name="title_text_color">@*android:color/primary_text_dark</color>
<color name="thumb_off">#BFFFFFFF</color>
<color name="track_off">@*android:color/material_grey_600</color>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
index 8194bdd..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
@@ -17,7 +17,6 @@
<resources>
- <color name="title_text_color">@*android:color/primary_text_light</color>
<color name="thumb_off">#BFFFFFFF</color>
<color name="track_off">@*android:color/material_grey_600</color>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index dd443de..b145c9b 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -18,13 +18,13 @@
<resources>
<!-- Size of layout margin left -->
- <dimen name="switchbar_margin_start">26dp</dimen>
+ <dimen name="switchbar_margin_start">22dp</dimen>
<!-- Size of layout margin right -->
- <dimen name="switchbar_margin_end">24dp</dimen>
+ <dimen name="switchbar_margin_end">16dp</dimen>
<!-- Minimum width of switch -->
- <dimen name="min_switch_width">48dp</dimen>
+ <dimen name="min_switch_width">52dp</dimen>
<!-- Minimum width of switch bar -->
<dimen name="min_switch_bar_height">72dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
index fbb896c..59b5899 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
@@ -19,7 +19,6 @@
<style name="MainSwitchText">
<item name="android:textSize">20sp</item>
- <item name="android:textColor">@color/title_text_color</item>
</style>
<style name="Settings.MainSwitch" parent="@android:style/Widget.Material.CompoundButton.Switch">
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 532c996..74b6578 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -136,10 +136,8 @@
* Show the MainSwitchBar
*/
public void show() {
- if (!isShowing()) {
- setVisibility(View.VISIBLE);
- mSwitch.setOnCheckedChangeListener(this);
- }
+ setVisibility(View.VISIBLE);
+ mSwitch.setOnCheckedChangeListener(this);
}
/**
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index dae0e70..274bf8d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -17,9 +17,11 @@
package com.android.settingslib.widget;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
import android.util.AttributeSet;
-import android.widget.Switch;
+import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.TwoStatePreference;
@@ -38,30 +40,29 @@
private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
private MainSwitchBar mMainSwitchBar;
- private Switch mSwitch;
private String mTitle;
private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
public MainSwitchPreference(Context context) {
super(context);
- init();
+ init(context, null);
}
public MainSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- init();
+ init(context, attrs);
}
public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- init();
+ init(context, attrs);
}
public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- init();
+ init(context, attrs);
}
@Override
@@ -76,8 +77,21 @@
registerListenerToSwitchBar();
}
- private void init() {
+ private void init(Context context, AttributeSet attrs) {
setLayoutResource(R.layout.main_switch_layout);
+
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/,
+ 0 /*defStyleRes*/);
+ final CharSequence title = TypedArrayUtils.getText(a,
+ androidx.preference.R.styleable.Preference_title,
+ androidx.preference.R.styleable.Preference_android_title);
+ if (!TextUtils.isEmpty(title)) {
+ setTitle(title.toString());
+ }
+ a.recycle();
+ }
}
/**
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index f346a59..3331550 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -6,6 +6,7 @@
static_libs: [
"androidx.preference_preference",
+ "androidx-constraintlayout_constraintlayout",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
index 9dbd5fa..f45105d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
+++ b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
@@ -17,6 +17,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@@ -26,29 +27,40 @@
android:paddingTop="32dp"
android:paddingBottom="32dp">
- <RelativeLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/usage_summary"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_alignBaseline="@id/total_summary"
- android:singleLine="true"
+ app:layout_constraintWidth_percent="0.45"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBaseline_toBaselineOf="@id/total_summary"
android:ellipsize="marquee"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Display1"
- android:textSize="20sp"/>
+ android:textSize="14sp"
+ android:textAlignment="viewStart"/>
<TextView
android:id="@+id/total_summary"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:singleLine="true"
+ app:layout_constraintWidth_percent="0.45"
+ app:layout_constraintEnd_toStartOf="@id/custom_content"
android:ellipsize="marquee"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"/>
- </RelativeLayout>
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
+ android:textSize="14sp"
+ android:textAlignment="viewEnd"/>
+ <FrameLayout
+ android:id="@+id/custom_content"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/total_summary"
+ app:layout_constraintWidth_percent="0.1"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
<ProgressBar
android:id="@android:id/progress"
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 950a8b4..af64a1d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -22,6 +22,9 @@
import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -41,6 +44,7 @@
private CharSequence mUsageSummary;
private CharSequence mTotalSummary;
+ private ImageView mCustomImageView;
private int mPercent = -1;
/**
@@ -110,6 +114,15 @@
notifyChanged();
}
+ /** Set custom ImageView to the right side of total summary. */
+ public <T extends ImageView> void setCustomContent(T imageView) {
+ if (imageView == mCustomImageView) {
+ return;
+ }
+ mCustomImageView = imageView;
+ notifyChanged();
+ }
+
/**
* Binds the created View to the data for this preference.
*
@@ -141,6 +154,15 @@
progressBar.setIndeterminate(false);
progressBar.setProgress(mPercent);
}
+
+ final FrameLayout customLayout = (FrameLayout) holder.findViewById(R.id.custom_content);
+ if (mCustomImageView == null) {
+ customLayout.removeAllViews();
+ customLayout.setVisibility(View.GONE);
+ } else {
+ customLayout.addView(mCustomImageView);
+ customLayout.setVisibility(View.VISIBLE);
+ }
}
private CharSequence enlargeFontOfNumber(CharSequence summary) {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5563003..7556ace 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1484,5 +1484,5 @@
<!-- Content description of the Ethernet connection when disconnected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_ethernet_disconnected">Ethernet disconnected.</string>
<!-- Content description of the Ethernet connection when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_ethernet_connected">Ethernet connected.</string>
+ <string name="accessibility_ethernet_connected">Ethernet.</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index f83c7a2..6cf53d0b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -479,7 +479,7 @@
}
}
- class RouterManagerCallback extends MediaRouter2Manager.Callback {
+ class RouterManagerCallback implements MediaRouter2Manager.Callback {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 5580afe..78282fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -38,7 +39,9 @@
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Track status of Wi-Fi for the Sys UI.
@@ -50,6 +53,7 @@
private final NetworkScoreManager mNetworkScoreManager;
private final ConnectivityManager mConnectivityManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Set<Integer> mNetworks = new HashSet<>();
private final WifiNetworkScoreCache.CacheListener mCacheListener =
new WifiNetworkScoreCache.CacheListener(mHandler) {
@Override
@@ -64,6 +68,20 @@
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(
+ Network network, NetworkCapabilities networkCapabilities,
+ LinkProperties linkProperties, boolean blocked) {
+ boolean isVcnOverWifi =
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && (Utils.tryGetWifiInfoForVcn(networkCapabilities) != null);
+ boolean isWifi =
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+ if (isVcnOverWifi || isWifi) {
+ mNetworks.add(network.getNetId());
+ }
+ }
+
// Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
// and onLinkPropertiesChanged.
@Override
@@ -84,9 +102,12 @@
@Override
public void onLost(Network network) {
- updateWifiInfo(null);
- updateStatusLabel();
- mCallback.run();
+ if (mNetworks.contains(network.getNetId())) {
+ mNetworks.remove(network.getNetId());
+ updateWifiInfo(null);
+ updateStatusLabel();
+ mCallback.run();
+ }
}
};
private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
index 85e2174..1a8477d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
@@ -18,11 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.text.SpannedString;
import android.text.style.RelativeSizeSpan;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -97,4 +101,30 @@
.findViewById(android.R.id.progress);
assertThat(progressBar.getProgress()).isEqualTo((int) (31.0f / 80 * 100));
}
+
+ @Test
+ public void setCustomContent_setNullImageView_noChild() {
+ mUsageProgressBarPreference.setCustomContent(null /* imageView */);
+
+ mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+ final FrameLayout customContent =
+ (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+ assertThat(customContent.getChildCount()).isEqualTo(0);
+ assertThat(customContent.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setCustomContent_setImageView_oneChild() {
+ final ImageView imageView = mock(ImageView.class);
+ mUsageProgressBarPreference.setCustomContent(imageView);
+
+ mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+ final FrameLayout customContent =
+ (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+ assertThat(customContent.getChildCount()).isEqualTo(1);
+ assertThat(customContent.getChildAt(0)).isEqualTo(imageView);
+ assertThat(customContent.getVisibility()).isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 60620bd..e92591d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -38,6 +38,7 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
Settings.Secure.ADAPTIVE_SLEEP,
+ Settings.Secure.CAMERA_AUTOROTATE,
Settings.Secure.AUTOFILL_SERVICE,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 66165b6..ad6a531 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -147,5 +147,10 @@
VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.NOTIFICATION_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.RESTRICTED_NETWORKING_MODE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Global.ONE_HANDED_KEYGUARD_SIDE,
+ new InclusiveIntegerRangeValidator(
+ /* first= */Global.ONE_HANDED_KEYGUARD_SIDE_LEFT,
+ /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 3f46262..ed2b6c9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -57,6 +57,7 @@
VALIDATORS.put(
Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
VALIDATORS.put(
Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 83ded43..1ce738e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1969,6 +1969,12 @@
Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
SecureSettingsProto.BLUETOOTH_ON_WHILE_DRIVING);
+ final long smartAutoRotateToken = p.start(SecureSettingsProto.CAMERA_AUTOROTATE);
+ dumpSetting(s, p,
+ Settings.Secure.CAMERA_AUTOROTATE,
+ SecureSettingsProto.CameraAutorotate.ENABLED);
+ p.end(smartAutoRotateToken);
+
final long cameraToken = p.start(SecureSettingsProto.CAMERA);
dumpSetting(s, p,
Settings.Secure.CAMERA_GESTURE_DISABLED,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index efd941e..c11877a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -272,6 +272,7 @@
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
+ Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
Settings.Global.ENHANCED_4G_MODE_ENABLED,
Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
Settings.Global.ERROR_LOGCAT_PREFIX,
@@ -280,7 +281,9 @@
Settings.Global.EUICC_UNSUPPORTED_COUNTRIES,
Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
+ Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS,
Settings.Global.FANCY_IME_ANIMATIONS,
+ Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
Settings.Global.FORCED_APP_STANDBY_ENABLED,
Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index ca9dcd6..5af0244 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -116,7 +116,6 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.MONITOR_INPUT" />
<uses-permission android:name="android.permission.INPUT_CONSUMER" />
- <uses-permission android:name="android.permission.USE_BACKGROUND_BLUR" />
<!-- DreamManager -->
<uses-permission android:name="android.permission.READ_DREAM_STATE" />
diff --git a/packages/SystemUI/docs/sos_gesture.md b/packages/SystemUI/docs/sos_gesture.md
new file mode 100644
index 0000000..1a9144b
--- /dev/null
+++ b/packages/SystemUI/docs/sos_gesture.md
@@ -0,0 +1,26 @@
+# How 5-tapping power launches Emergency Sos
+
+_as of Jan 2021_
+
+Note that the flow is a simplified version of the camera launch flow.
+
+
+### Sequence of events
+
+
+1. [PhoneWindowManager.java](/services/core/java/com/android/server/policy/PhoneWindowManager.java) is responsible for all power button presses (see `interceptPowerKeyDown`).
+2. Even though PWMgr has a lot of logic to detect all manner of power button multipresses and gestures, it also checks with GestureLauncherService, which is also [offered the chance](/services/core/java/com/android/server/policy/PhoneWindowManager.java#943) to [intercept](/services/core/java/com/android/server/GestureLauncherService.java#358) the power key.
+3. GLS is responsible for the emergoncy sos timeout, and if it detects one, it [forwards it to the StatusBarManagerService](/services/core/java/com/android/server/GestureLauncherService.java#475) (which hands it off to SystemUI).
+4. Inside SystemUI, [onEmergencyActionLaunchGestureDetected](/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java#4039) and determines
+ 1. If the gesture is enabled (else do nothing)
+ 2. If there is an app to handle the gesture (else do nothing)
+ 2. whether the screen is on; if not, we need to delay until that happens
+5. Assuming there is an app, and the setting is one launch Emergengy Flow immediately. [Callsite](/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java#4077)
+ 1. Note that we cannot have an intent resolver, so we launch the default.
+
+**Which intent launches?**
+
+Due to the nature of the gesture, we need the flow to work behind the lockscreen, and without disambiguation.
+Thus, we always launch the same intent, and verify that there is only one matching intent-filter in the system image.
+
+[The emergengy sos intent action](packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java#36).
diff --git a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
index 99c70a5..b96c07e 100644
--- a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
+++ b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
@@ -17,9 +17,8 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/wallpaperTextColorSecondary">
<item android:id="@android:id/background">
- <shape
- android:color="@android:color/transparent">
- <stroke android:width="1dp" android:color="?android:attr/textColorSecondary"/>
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorAccent"/>
<corners android:radius="24dp"/>
</shape>
</item>
@@ -29,4 +28,4 @@
<corners android:radius="24dp"/>
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
index 7986809..b5f55af 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
@@ -24,7 +24,7 @@
<include
style="@style/BouncerSecurityContainer"
layout="@layout/keyguard_host_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 370576b..0ee1b69 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -43,7 +43,7 @@
<com.android.keyguard.EmergencyButton
android:id="@+id/emergency_call_button"
android:layout_width="wrap_content"
- android:layout_height="32dp"
+ android:layout_height="48dp"
android:layout_marginBottom="12dp"
android:text="@*android:string/lockscreen_emergency_call"
style="@style/Keyguard.TextView.EmergencyButton" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index 04e645b..c75ee51 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -41,13 +41,14 @@
android:layout_gravity="center">
<com.android.keyguard.KeyguardSecurityViewFlipper
android:id="@+id/view_flipper"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="@dimen/keyguard_security_view_top_margin"
android:paddingStart="@dimen/keyguard_security_view_lateral_margin"
android:paddingEnd="@dimen/keyguard_security_view_lateral_margin"
+ android:layout_gravity="center"
android:gravity="center">
</com.android.keyguard.KeyguardSecurityViewFlipper>
</com.android.keyguard.KeyguardSecurityContainer>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index dc2d11d..1a38585 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -67,6 +67,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
android:gravity="center_horizontal" />
</LinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index aa14645a..6ae759c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -188,6 +188,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
android:gravity="center_horizontal"/>
</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index 64ccefd..f709424 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -198,6 +198,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
android:gravity="center_horizontal"/>
</com.android.keyguard.KeyguardSimPinView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index 70f495c..2f9fed6 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -199,5 +199,6 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
android:gravity="center_horizontal"/>
</com.android.keyguard.KeyguardSimPukView>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
new file mode 100644
index 0000000..e09bf7e
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.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.
+ -->
+
+<resources>
+ <bool name="can_use_one_handed_bouncer">true</bool>
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index 8d9d6ee..6176f7c 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -22,4 +22,5 @@
<!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
+ <bool name="can_use_one_handed_bouncer">false</bool>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index aa87107..a928b75 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -40,6 +40,8 @@
<dimen name="keyguard_security_view_top_margin">8dp</dimen>
<dimen name="keyguard_security_view_lateral_margin">36dp</dimen>
+ <dimen name="keyguard_eca_top_margin">24dp</dimen>
+
<!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text.
Should be 0 on devices with plenty of room (e.g. tablets) -->
<dimen name="eca_overlap">-10dip</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 2e99dea..cd82b80 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -23,12 +23,13 @@
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
- <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
<item name="android:textSize">14dp</item>
<item name="android:background">@drawable/kg_emergency_button_background</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:paddingLeft">12dp</item>
<item name="android:paddingRight">12dp</item>
+ <item name="android:stateListAnimator">@null</item>
</style>
<style name="NumPadKey" parent="Theme.SystemUI">
<item name="android:colorControlNormal">?android:attr/colorBackground</item>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
index 108591b..d097472 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
@@ -15,22 +15,24 @@
~ limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
- android:paddingMode="stack">
+ android:paddingMode="stack" >
<item android:id="@android:id/background"
android:gravity="center_vertical|fill_horizontal">
- <layer-list >
+ <layer-list>
<item>
<shape
android:tint="?android:attr/colorControlActivated"
android:alpha="?android:attr/disabledAlpha">
- <size android:height="48dp" />
+ <size android:height="@dimen/rounded_slider_height" />
<solid android:color="@color/white_disabled" />
- <corners android:radius="24dp" />
+ <corners android:radius="@dimen/rounded_slider_corner_radius" />
</shape>
</item>
<item
- android:gravity="center_vertical|start"
- android:start="32dp">
+ android:gravity="center_vertical|left"
+ android:height="@dimen/rounded_slider_icon_size"
+ android:width="@dimen/rounded_slider_icon_size"
+ android:left="@dimen/rounded_slider_icon_inset">
<com.android.systemui.util.AlphaTintDrawableWrapper
android:drawable="@drawable/ic_brightness"
android:tint="?android:attr/colorControlActivated" />
@@ -39,10 +41,8 @@
</item>
<item android:id="@android:id/progress"
android:gravity="center_vertical|fill_horizontal">
- <clip
- android:drawable="@drawable/brightness_progress_full_drawable"
- android:clipOrientation="horizontal"
- android:gravity="left"
- />
+ <com.android.systemui.util.RoundedCornerProgressDrawable
+ android:drawable="@drawable/brightness_progress_full_drawable"
+ />
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
index b5def5e..41140a7 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
@@ -15,18 +15,21 @@
~ limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
<item android:id="@+id/slider_foreground">
<shape>
- <size android:height="48dp" />
+ <size android:height="@dimen/rounded_slider_height" />
<solid android:color="?android:attr/colorControlActivated" />
- <corners android:radius="24dp"/>
+ <corners android:radius="@dimen/rounded_slider_corner_radius"/>
</shape>
</item>
<item
android:id="@+id/slider_icon"
- android:gravity="center_vertical|start"
- android:start="32dp">
+ android:gravity="center_vertical|right"
+ android:height="@dimen/rounded_slider_icon_size"
+ android:width="@dimen/rounded_slider_icon_size"
+ android:right="@dimen/rounded_slider_icon_inset">
<com.android.systemui.util.AlphaTintDrawableWrapper
android:drawable="@drawable/ic_brightness"
android:tint="?android:attr/colorBackground"
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
index cf64136..5cb6f46 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
@@ -21,16 +21,16 @@
>
<shape android:shape="oval">
<size
- android:height="28dp"
- android:width="28dp"
+ android:height="@dimen/ongoing_appops_dialog_circle_size"
+ android:width="@dimen/ongoing_appops_dialog_circle_size"
/>
<solid android:color="@color/privacy_circle_camera" />
</shape>
</item>
<item android:id="@id/icon"
android:gravity="center"
- android:width="20dp"
- android:height="20dp"
+ android:width="@dimen/ongoing_appops_dialog_icon_size"
+ android:height="@dimen/ongoing_appops_dialog_icon_size"
android:drawable="@*android:drawable/perm_group_camera"
/>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
index 0a6a4a3..bff9b4b 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
@@ -21,16 +21,16 @@
>
<shape android:shape="oval">
<size
- android:height="28dp"
- android:width="28dp"
+ android:height="@dimen/ongoing_appops_dialog_circle_size"
+ android:width="@dimen/ongoing_appops_dialog_circle_size"
/>
<solid android:color="@color/privacy_circle_microphone_location" />
</shape>
</item>
<item android:id="@id/icon"
android:gravity="center"
- android:width="20dp"
- android:height="20dp"
- android:drawable="@*android:drawable/perm_group_microphone"
+ android:width="@dimen/ongoing_appops_dialog_icon_size"
+ android:height="@dimen/ongoing_appops_dialog_icon_size"
+ android:drawable="@*android:drawable/perm_group_location"
/>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
index 0a6a4a3..28466c8 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
@@ -21,16 +21,16 @@
>
<shape android:shape="oval">
<size
- android:height="28dp"
- android:width="28dp"
+ android:height="@dimen/ongoing_appops_dialog_circle_size"
+ android:width="@dimen/ongoing_appops_dialog_circle_size"
/>
<solid android:color="@color/privacy_circle_microphone_location" />
</shape>
</item>
<item android:id="@id/icon"
android:gravity="center"
- android:width="20dp"
- android:height="20dp"
+ android:width="@dimen/ongoing_appops_dialog_icon_size"
+ android:height="@dimen/ongoing_appops_dialog_icon_size"
android:drawable="@*android:drawable/perm_group_microphone"
/>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
index 59dad0e..b8ea622 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
@@ -17,6 +17,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid
- android:color="?android:attr/textColorPrimary" />
+ android:color="?android:attr/textColorSecondary" />
<corners android:radius="2dp" />
</shape>
diff --git a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
index 71b414f..93664da 100644
--- a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
@@ -25,7 +25,7 @@
android:layout_marginBottom="@dimen/screenshot_offset_y"
android:scaleType="fitStart"
android:elevation="@dimen/screenshot_preview_elevation"
- android:visibility="gone"
+ android:visibility="invisible"
android:background="@drawable/screenshot_rounded_corners"
android:adjustViewBounds="true"
android:contentDescription="@string/screenshot_edit_label"
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index fc68a64..8f3345f 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -22,63 +22,88 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <ImageView
- android:id="@+id/preview"
+ <Button
+ android:id="@+id/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginVertical="8dp"
- android:layout_marginHorizontal="48dp"
- android:adjustViewBounds="true"
- app:layout_constrainedHeight="true"
- app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toBottomOf="@id/guideline"
- app:layout_constraintEnd_toEndOf="parent"
+ android:text="@string/save"
+ app:layout_constraintEnd_toStartOf="@id/cancel"
+ app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- tools:background="?android:colorBackground"
- tools:minHeight="100dp"
- tools:minWidth="100dp" />
+ app:layout_constraintBottom_toTopOf="@id/guideline" />
+
+ <Button
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/cancel"
+ app:layout_constraintEnd_toStartOf="@id/edit"
+ app:layout_constraintStart_toEndOf="@id/save"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline" />
+
+ <Button
+ android:id="@+id/edit"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/screenshot_edit_label"
+ app:layout_constraintEnd_toStartOf="@id/share"
+ app:layout_constraintStart_toEndOf="@id/cancel"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline" />
+
+ <Button
+ android:id="@+id/share"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@*android:string/share"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/edit"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.9" />
+ app:layout_constraintGuide_percent="0.1" />
- <Button
- android:id="@+id/close"
+ <ImageView
+ android:id="@+id/preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
- android:text="Close"
- app:layout_constraintEnd_toStartOf="@+id/edit"
- app:layout_constraintHorizontal_bias="0.5"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/guideline" />
-
- <Button
- android:id="@+id/edit"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="8dp"
- android:text="Edit"
- app:layout_constraintEnd_toStartOf="@+id/share"
- app:layout_constraintHorizontal_bias="0.5"
- app:layout_constraintStart_toEndOf="@+id/close"
- app:layout_constraintTop_toTopOf="@+id/guideline" />
-
- <Button
- android:id="@+id/share"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="8dp"
- android:text="Share"
+ android:layout_marginBottom="24dp"
+ android:layout_marginHorizontal="48dp"
+ android:adjustViewBounds="true"
+ app:layout_constrainedHeight="true"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintTop_toBottomOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.5"
- app:layout_constraintStart_toEndOf="@+id/edit"
- app:layout_constraintTop_toTopOf="@+id/guideline" />
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ tools:background="?android:colorBackground"
+ tools:minHeight="100dp"
+ tools:minWidth="100dp" />
+
+ <com.android.systemui.screenshot.CropView
+ android:id="@+id/crop_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ app:layout_constrainedHeight="true"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintTop_toBottomOf="@id/guideline"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:handleThickness="3dp"
+ app:handleColor="@*android:color/accent_device_default"
+ app:scrimColor="#9444"
+ tools:background="?android:colorBackground"
+ tools:minHeight="100dp"
+ tools:minWidth="100dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 5db247e..4d77a0d 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -22,7 +22,13 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/ongoing_appops_dialog_side_margins"
android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
+ android:layout_marginTop="8dp"
android:orientation="vertical"
- android:padding = "8dp"
+ android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
+ android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
+ android:paddingBottom="12dp"
+ android:paddingTop="8dp"
android:background="@drawable/privacy_dialog_bg"
-/>
\ No newline at end of file
+/>
+<!-- 12dp padding bottom so there's 20dp total under the icon -->
+<!-- 8dp padding top, as there's 4dp margin in each row -->
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index 882e968..91ffe22 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -16,19 +16,22 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/privacy_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:orientation="horizontal"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="4dp"
+ android:importantForAccessibility="yes"
+ android:focusable="true"
>
+ <!-- 4dp marginTop makes 20dp minimum between icons -->
<ImageView
android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/ongoing_appops_dialog_circle_size"
+ android:layout_height="@dimen/ongoing_appops_dialog_circle_size"
android:layout_gravity="center_vertical"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
+ android:importantForAccessibility="no"
/>
<TextView
@@ -38,25 +41,21 @@
android:layout_width="0dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:textDirection="locale"
+ android:textAlignment="viewStart"
android:gravity="start | center_vertical"
- android:textAppearance="@style/TextAppearance.QS.TileLabel"
+ android:textAppearance="@style/TextAppearance.PrivacyDialog"
+ android:lineHeight="20sp"
/>
- <FrameLayout
- android:id="@+id/link"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ <ImageView
+ android:layout_height="24dp"
+ android:layout_width="24dp"
android:layout_gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- >
-
- <ImageView
- android:layout_height="24dp"
- android:layout_width="24dp"
- android:layout_gravity="center"
- android:src="@*android:drawable/ic_chevron_end"
- android:tint="?android:attr/textColorPrimary"
- />
- </FrameLayout>
+ android:src="@*android:drawable/ic_chevron_end"
+ android:tint="?android:attr/textColorPrimary"
+ />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 75f76b4..d6385ff 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -50,18 +50,22 @@
android:layout="@layout/qs_panel"
android:layout_width="@dimen/qs_panel_width"
android:layout_height="match_parent"
- android:layout_gravity="@integer/notification_panel_layout_gravity"
android:clipToPadding="false"
android:clipChildren="false"
- systemui:viewType="com.android.systemui.plugins.qs.QS" />
+ systemui:viewType="com.android.systemui.plugins.qs.QS"
+ systemui:layout_constraintStart_toStartOf="parent"
+ systemui:layout_constraintEnd_toEndOf="parent"
+ />
<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
android:layout_marginTop="@dimen/notification_panel_margin_top"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
- android:layout_gravity="@integer/notification_panel_layout_gravity"
- android:layout_marginBottom="@dimen/close_handle_underlap" />
+ android:layout_marginBottom="@dimen/close_handle_underlap"
+ systemui:layout_constraintStart_toStartOf="parent"
+ systemui:layout_constraintEnd_toEndOf="parent"
+ />
<include layout="@layout/ambient_indication"
android:id="@+id/ambient_indication_container" />
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/packages/SystemUI/res/layout/udfps_animation_view.xml
new file mode 100644
index 0000000..380dd85
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_animation_view.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.
+ -->
+<com.android.systemui.biometrics.UdfpsAnimationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/udfps_animation_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/layout/udfps_surface_view.xml b/packages/SystemUI/res/layout/udfps_surface_view.xml
new file mode 100644
index 0000000..18858d6
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_surface_view.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.
+ -->
+<com.android.systemui.biometrics.UdfpsSurfaceView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/udfps_surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index dec83d9..722f148 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -39,7 +39,6 @@
<item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
<item>@string/config_systemUIVendorServiceComponent</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
- <item>com.android.systemui.SizeCompatModeActivityController</item>
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
<item>com.android.systemui.toast.ToastUI</item>
<item>com.android.systemui.wmshell.WMShell</item>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 4059b49..6c55fb6 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -171,5 +171,15 @@
<declare-styleable name="PagedTileLayout">
<attr name="sideLabels" format="boolean"/>
</declare-styleable>
+
+ <declare-styleable name="CropView">
+ <attr name="handleThickness" format="dimension" />
+ <attr name="handleColor" format="color" />
+ <attr name="scrimColor" format="color" />
+ </declare-styleable>
+
+ <declare-styleable name="RoundedCornerProgressDrawable">
+ <attr name="android:drawable" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 09710d7..44eeba1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -301,7 +301,6 @@
<item>com.android.systemui.ScreenDecorations</item>
<item>com.android.systemui.biometrics.AuthController</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
- <item>com.android.systemui.SizeCompatModeActivityController</item>
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
<item>com.android.systemui.theme.ThemeOverlayController</item>
<item>com.android.systemui.accessibility.WindowMagnification</item>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e510930..d92f4ea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1181,6 +1181,12 @@
<dimen name="ongoing_appops_dialog_side_margins">@dimen/notification_shade_content_margin_horizontal</dimen>
+ <dimen name="ongoing_appops_dialog_circle_size">32dp</dimen>
+
+ <dimen name="ongoing_appops_dialog_icon_size">20dp</dimen>
+
+ <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
+
<!-- Size of the RAT type for CellularTile -->
<dimen name="celltile_rat_type_size">10sp</dimen>
@@ -1327,4 +1333,12 @@
<dimen name="people_space_widget_radius">24dp</dimen>
<dimen name="people_space_widget_round_radius">100dp</dimen>
<dimen name="people_space_widget_background_padding">6dp</dimen>
+
+ <dimen name="rounded_slider_height">48dp</dimen>
+ <!-- rounded_slider_height / 2 -->
+ <dimen name="rounded_slider_corner_radius">24dp</dimen>
+ <!-- rounded_slider_height / 2 -->
+ <dimen name="rounded_slider_icon_size">24dp</dimen>
+ <!-- rounded_slider_icon_size / 2 -->
+ <dimen name="rounded_slider_icon_inset">12dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cb7327f..d932395 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1888,7 +1888,7 @@
<!-- Notification Inline controls: describes how the notification was adjusted [CHAR_LIMIT=NONE] -->
<string name="feedback_demoted">This notification was automatically <b>ranked lower</b> in your shade.</string>
<!-- Notification Inline controls: prompts the user for feedback [CHAR_LIMIT=NONE] -->
- <string name="feedback_prompt">Was this correct?</string>
+ <string name="feedback_prompt">Let the developer know your feedback. Was this correct?</string>
<!-- Notification Inline controls: responds to user provided feedback [CHAR_LIMIT=NONE] -->
<string name="feedback_response">Thanks for your feedback!</string>
<string name="feedback_ok">OK</string>
@@ -2580,9 +2580,6 @@
<!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] -->
<string name="music_controls_no_title">No title</string>
- <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
- <string name="restart_button_description">Tap to restart this app and go full screen.</string>
-
<!-- Action in accessibility menu to move the stack of bubbles [CHAR LIMIT=20] -->
<string name="bubble_accessibility_action_move">Move</string>
@@ -2771,13 +2768,6 @@
<!-- Controls menu, edit [CHAR_LIMIT=30] -->
<string name="controls_menu_edit">Edit controls</string>
- <!-- Device-specific path to the node for enabling/disabling the high-brightness mode -->
- <string name="udfps_hbm_sysfs_path" translatable="false"></string>
- <!-- Device-specific payload for enabling the high-brightness mode -->
- <string name="udfps_hbm_enable_command" translatable="false"></string>
- <!-- Device-specific payload for disabling the high-brightness mode -->
- <string name="udfps_hbm_disable_command" translatable="false"></string>
-
<!-- Title for the media output group dialog with media related devices [CHAR LIMIT=50] -->
<string name="media_output_dialog_add_output">Add outputs</string>
<!-- Title for the media output slice with group devices [CHAR LIMIT=50] -->
@@ -2826,4 +2816,6 @@
<!-- No translation [CHAR LIMIT=0] -->
<string name="qs_remove_labels" translatable="false"></string>
+
+ <string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamily</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index db260ce..4b04eeb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -196,7 +196,7 @@
<style name="TextAppearance.QS.TileLabel">
<item name="android:textSize">@dimen/qs_tile_text_size</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:fontFamily">@string/qs_tile_label_fontFamily</item>
</style>
<style name="TextAppearance.QS.TileLabel.Secondary">
@@ -354,15 +354,17 @@
</style>
<style name="LockPatternStyle">
- <item name="*android:regularColor">?android:attr/textColorPrimary</item>
+ <item name="*android:regularColor">?android:attr/colorAccent</item>
<item name="*android:successColor">?android:attr/textColorPrimary</item>
<item name="*android:errorColor">?android:attr/colorError</item>
+ <item name="*android:dotColor">?android:attr/textColorSecondary</item>
</style>
<style name="LockPatternStyleBiometricPrompt">
<item name="*android:regularColor">?android:attr/colorForeground</item>
<item name="*android:successColor">?android:attr/colorForeground</item>
<item name="*android:errorColor">?android:attr/colorError</item>
+ <item name="*android:dotColor">?android:attr/textColorSecondary</item>
</style>
<style name="qs_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
@@ -745,4 +747,9 @@
* Title: headline, medium 20sp
* Message: body, 16 sp -->
<style name="Theme.ControlsRequestDialog" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert"/>
+
+ <style name="TextAppearance.PrivacyDialog">
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index 10e28c4..d0c63a8 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -15,8 +15,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="180dp"
+ android:minWidth="140dp"
android:minHeight="40dp"
+ android:minResizeWidth="110dp"
+ android:minResizeHeight="40dp"
android:updatePeriodMillis="60000"
android:previewImage="@drawable/ic_android"
android:resizeMode="horizontal|vertical"
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
similarity index 67%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
index 19b20f2..e5ced3e 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+package com.android.systemui.shared.recents;
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ */
+oneway interface ISplitScreenListener {
+ void onStagePositionChanged(int stage, int position);
+ void onTaskStageChanged(int taskId, int stage);
+}
\ No newline at end of file
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 388eeb6..e38cf23 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
@@ -16,6 +16,7 @@
package com.android.systemui.shared.recents;
+import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
@@ -23,9 +24,11 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.UserHandle;
import android.view.MotionEvent;
import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.ISplitScreenListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteTransitionCompat;
@@ -202,4 +205,49 @@
/** Unegisters a RemoteTransitionCompat that will handle transitions. */
void unregisterRemoteTransition(in RemoteTransitionCompat remoteTransition) = 33;
+
+// SplitScreen APIs...copied from SplitScreen.java
+ /**
+ * Stage position isn't specified normally meaning to use what ever it is currently set to.
+ */
+ //int STAGE_POSITION_UNDEFINED = -1;
+ /**
+ * Specifies that a stage is positioned at the top half of the screen if
+ * in portrait mode or at the left half of the screen if in landscape mode.
+ */
+ //int STAGE_POSITION_TOP_OR_LEFT = 0;
+ /**
+ * Specifies that a stage is positioned at the bottom half of the screen if
+ * in portrait mode or at the right half of the screen if in landscape mode.
+ */
+ //int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ //int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ * @see MainStage
+ */
+ //int STAGE_TYPE_MAIN = 0;
+ /**
+ * The side stage type.
+ * @see SideStage
+ */
+ //int STAGE_TYPE_SIDE = 1;
+
+ void registerSplitScreenListener(in ISplitScreenListener listener) = 34;
+ void unregisterSplitScreenListener(in ISplitScreenListener listener) = 35;
+
+ /** Hides the side-stage if it is currently visible. */
+ 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;
+ void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
+ in Bundle options, in UserHandle user) = 39;
+ void startIntent(
+ in PendingIntent intent, in int stage, in int position, in Bundle options) = 40;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 2a0715e..87f6b82 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -118,9 +118,9 @@
}
public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
- final RemoteAnimationTargetCompat[] appsCompat =
- new RemoteAnimationTargetCompat[apps != null ? apps.length : 0];
- for (int i = 0; i < apps.length; i++) {
+ final int length = apps != null ? apps.length : 0;
+ final RemoteAnimationTargetCompat[] appsCompat = new RemoteAnimationTargetCompat[length];
+ for (int i = 0; i < length; i++) {
appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
}
return appsCompat;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 8d010c7..6c77af7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -19,7 +19,6 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ITaskStackListener;
import android.content.ComponentName;
-import android.os.IBinder;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -80,7 +79,6 @@
public void onTaskDescriptionChanged(RunningTaskInfo taskInfo) { }
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
- public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { }
public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index a907e66..8f08f5a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -18,15 +18,14 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
-import android.window.TaskSnapshot;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Trace;
import android.util.Log;
+import android.window.TaskSnapshot;
import com.android.internal.os.SomeArgs;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -88,13 +87,12 @@
private static final int ON_TASK_MOVED_TO_FRONT = 14;
private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 15;
private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 16;
- private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 17;
- private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 18;
- private static final int ON_TASK_DISPLAY_CHANGED = 19;
- private static final int ON_TASK_LIST_UPDATED = 20;
- private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 21;
- private static final int ON_TASK_DESCRIPTION_CHANGED = 22;
- private static final int ON_ACTIVITY_ROTATION = 23;
+ private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 17;
+ private static final int ON_TASK_DISPLAY_CHANGED = 18;
+ private static final int ON_TASK_LIST_UPDATED = 19;
+ private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 20;
+ private static final int ON_TASK_DESCRIPTION_CHANGED = 21;
+ private static final int ON_ACTIVITY_ROTATION = 22;
/**
* List of {@link TaskStackChangeListener} registered from {@link #addListener}.
@@ -248,13 +246,6 @@
}
@Override
- public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
- mHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
- 0 /* unused */,
- activityToken).sendToTarget();
- }
-
- @Override
public void onTaskDisplayChanged(int taskId, int newDisplayId) {
mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
}
@@ -391,13 +382,6 @@
}
break;
}
- case ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onSizeCompatModeActivityChanged(
- msg.arg1, (IBinder) msg.obj);
- }
- break;
- }
case ON_BACK_PRESSED_ON_TASK_ROOT: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onBackPressedOnTaskRoot(
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index b7d7498..707ee29 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -154,7 +154,7 @@
**/
public void reloadColors() {
int color = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.textColorSecondary);
+ android.R.attr.textColorPrimaryInverse);
setTextColor(color);
setBackground(getContext()
.getDrawable(com.android.systemui.R.drawable.kg_emergency_button_background));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index cc59c39..c182fd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -29,12 +29,17 @@
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.util.TypedValue;
+import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.OrientationEventListener;
import android.view.VelocityTracker;
+import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimationControlListener;
@@ -55,6 +60,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import java.util.List;
@@ -99,6 +105,12 @@
private boolean mDisappearAnimRunning;
private SwipeListener mSwipeListener;
+ private boolean mIsSecurityViewLeftAligned = true;
+ private boolean mOneHandedMode = false;
+ private SecurityMode mSecurityMode = SecurityMode.Invalid;
+ private ViewPropertyAnimator mRunningOneHandedAnimator;
+ private final OrientationEventListener mOrientationEventListener;
+
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -157,16 +169,20 @@
// Used to notify the container when something interesting happens.
public interface SecurityCallback {
boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen);
+
void userActivity();
+
void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
/**
- * @param strongAuth wheher the user has authenticated with strong authentication like
- * pattern, password or PIN but not by trust agents or fingerprint
+ * @param strongAuth wheher the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
* @param targetUserId a user that needs to be the foreground user at the finish completion.
*/
void finish(boolean strongAuth, int targetUserId);
+
void reset();
+
void onCancelClicked();
}
@@ -224,11 +240,136 @@
super(context, attrs, defStyle);
mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
mViewConfiguration = ViewConfiguration.get(context);
+
+ mOrientationEventListener = new OrientationEventListener(context) {
+ @Override
+ public void onOrientationChanged(int orientation) {
+ updateLayoutForSecurityMode(mSecurityMode);
+ }
+ };
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
+ mSecurityMode = securityMode;
mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
updateBiometricRetry(securityMode, faceAuthEnabled);
+
+ updateLayoutForSecurityMode(securityMode);
+ mOrientationEventListener.enable();
+ }
+
+ void updateLayoutForSecurityMode(SecurityMode securityMode) {
+ mSecurityMode = securityMode;
+ mOneHandedMode = canUseOneHandedBouncer();
+
+ if (mOneHandedMode) {
+ mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext);
+ }
+
+ updateSecurityViewGravity();
+ updateSecurityViewLocation(false);
+ }
+
+ /** Return whether the one-handed keyguard should be enabled. */
+ private boolean canUseOneHandedBouncer() {
+ // Is it enabled?
+ if (!getResources().getBoolean(
+ com.android.internal.R.bool.config_enableOneHandedKeyguard)) {
+ return false;
+ }
+
+ if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) {
+ return false;
+ }
+
+ return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+ }
+
+ /** Read whether the one-handed keyguard should be on the left/right from settings. */
+ private boolean isOneHandedKeyguardLeftAligned(Context context) {
+ try {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
+ == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+ } catch (Settings.SettingNotFoundException ex) {
+ return true;
+ }
+ }
+
+ private void updateSecurityViewGravity() {
+ View securityView = findKeyguardSecurityView();
+
+ if (securityView == null) {
+ return;
+ }
+
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams();
+
+ if (mOneHandedMode) {
+ lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
+ } else {
+ lp.gravity = Gravity.CENTER;
+ }
+
+ securityView.setLayoutParams(lp);
+ }
+
+ /**
+ * Moves the inner security view to the correct location (in one handed mode) with animation.
+ * This is triggered when the user taps on the side of the screen that is not currently occupied
+ * by the security view .
+ */
+ private void updateSecurityViewLocation(boolean animate) {
+ View securityView = findKeyguardSecurityView();
+
+ if (securityView == null) {
+ return;
+ }
+
+ if (!mOneHandedMode) {
+ securityView.setTranslationX(0);
+ return;
+ }
+
+ if (mRunningOneHandedAnimator != null) {
+ mRunningOneHandedAnimator.cancel();
+ mRunningOneHandedAnimator = null;
+ }
+
+ int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f);
+
+ if (animate) {
+ mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation);
+ mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRunningOneHandedAnimator = null;
+ }
+ });
+
+ mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mRunningOneHandedAnimator.start();
+ } else {
+ securityView.setTranslationX(targetTranslation);
+ }
+ }
+
+ @Nullable
+ private KeyguardSecurityViewFlipper findKeyguardSecurityView() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+
+ if (isKeyguardSecurityView(child)) {
+ return (KeyguardSecurityViewFlipper) child;
+ }
+ }
+
+ return null;
+ }
+
+ private boolean isKeyguardSecurityView(View view) {
+ return view instanceof KeyguardSecurityViewFlipper;
}
public void onPause() {
@@ -237,6 +378,7 @@
mAlertDialog = null;
}
mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
+ mOrientationEventListener.disable();
}
@Override
@@ -318,19 +460,44 @@
if (mSwipeListener != null) {
mSwipeListener.onSwipeUp();
}
+ } else {
+ if (!mIsDragging) {
+ handleTap(event);
+ }
}
}
return true;
}
+ private void handleTap(MotionEvent event) {
+ // If we're using a fullscreen security mode, skip
+ if (!mOneHandedMode) {
+ return;
+ }
+
+ // Did the tap hit the "other" side of the bouncer?
+ if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f))
+ || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) {
+ mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned;
+
+ Settings.Global.putInt(
+ mContext.getContentResolver(),
+ Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
+ mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+ : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+
+ updateSecurityViewLocation(true);
+ }
+ }
+
void setSwipeListener(SwipeListener swipeListener) {
mSwipeListener = swipeListener;
}
private void startSpringAnimation(float startVelocity) {
mSpringAnimation
- .setStartVelocity(startVelocity)
- .animateToFinalPosition(0);
+ .setStartVelocity(startVelocity)
+ .animateToFinalPosition(0);
}
public void startDisappearAnimation(SecurityMode securitySelection) {
@@ -395,7 +562,9 @@
}
private void beginJankInstrument(int cuj) {
- InteractionJankMonitor.getInstance().begin(mSecurityViewFlipper.getSecurityView(), cuj);
+ KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
+ if (securityView == null) return;
+ InteractionJankMonitor.getInstance().begin(securityView, cuj);
}
private void endJankInstrument(int cuj) {
@@ -438,18 +607,17 @@
return insets.inset(0, 0, 0, inset);
}
-
private void showDialog(String title, String message) {
if (mAlertDialog != null) {
mAlertDialog.dismiss();
}
mAlertDialog = new AlertDialog.Builder(mContext)
- .setTitle(title)
- .setMessage(message)
- .setCancelable(false)
- .setNeutralButton(R.string.ok, null)
- .create();
+ .setTitle(title)
+ .setMessage(message)
+ .setCancelable(false)
+ .setNeutralButton(R.string.ok, null)
+ .create();
if (!(mContext instanceof Activity)) {
mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
}
@@ -487,6 +655,44 @@
}
}
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int maxHeight = 0;
+ int maxWidth = 0;
+ int childState = 0;
+
+ int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ MeasureSpec.getSize(widthMeasureSpec) / 2,
+ MeasureSpec.getMode(widthMeasureSpec));
+
+ for (int i = 0; i < getChildCount(); i++) {
+ final View view = getChildAt(i);
+ if (view.getVisibility() != GONE) {
+ if (mOneHandedMode && isKeyguardSecurityView(view)) {
+ measureChildWithMargins(view, halfWidthMeasureSpec, 0,
+ heightMeasureSpec, 0);
+ } else {
+ measureChildWithMargins(view, widthMeasureSpec, 0,
+ heightMeasureSpec, 0);
+ }
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ maxWidth = Math.max(maxWidth,
+ view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+ maxHeight = Math.max(maxHeight,
+ view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+ childState = combineMeasuredStates(childState, view.getMeasuredState());
+ }
+ }
+
+ // Check against our minimum height and width
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+ maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+ setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+ resolveSizeAndState(maxHeight, heightMeasureSpec,
+ childState << MEASURED_HEIGHT_STATE_SHIFT));
+ }
+
void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
String message = null;
switch (userType) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 1a8d420..fdab8db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -404,6 +404,7 @@
if (newView != null) {
newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
mSecurityViewFlipperController.show(newView);
+ mView.updateLayoutForSecurityMode(securityMode);
}
mSecurityCallback.onSecurityModeChanged(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index c77c867..631c248 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -92,4 +92,13 @@
throw new IllegalStateException("Unknown security quality:" + security);
}
}
+
+ /**
+ * Returns whether the given security view should be used in a "one handed" way. This can be
+ * used to change how the security view is drawn (e.g. take up less of the screen, and align to
+ * one side).
+ */
+ public static boolean isSecurityViewOneHanded(SecurityMode securityMode) {
+ return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 7773fe9..75ef4b32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -128,6 +128,8 @@
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
+ if (child.getVisibility() != View.VISIBLE) continue;
+
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.maxWidth > 0 && lp.maxWidth < maxWidth) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9908e67..d9a1eb6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1909,12 +1909,14 @@
}
/**
- * Whether to show the lock icon on lock screen and bouncer. This depends on the enrolled
- * biometrics to the device.
+ * Whether to show the lock icon on lock screen and bouncer.
*/
- public boolean shouldShowLockIcon() {
- return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
- && !isUdfpsEnrolled();
+ public boolean canShowLockIcon() {
+ if (mLockScreenMode == LOCK_SCREEN_MODE_LAYOUT_1) {
+ return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
+ && !isUdfpsEnrolled();
+ }
+ return true;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
deleted file mode 100644
index 5384ddf..0000000
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.app.ActivityClient;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
-import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.CommandQueue;
-
-import java.lang.ref.WeakReference;
-
-import javax.inject.Inject;
-
-/** Shows a restart-activity button when the foreground activity is in size compatibility mode. */
-@SysUISingleton
-public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {
- private static final String TAG = "SizeCompatMode";
-
- /** The showing buttons by display id. */
- private final SparseArray<RestartActivityButton> mActiveButtons = new SparseArray<>(1);
- /** Avoid creating display context frequently for non-default display. */
- private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
- private final CommandQueue mCommandQueue;
-
- /** Only show once automatically in the process life. */
- private boolean mHasShownHint;
-
- @VisibleForTesting
- @Inject
- SizeCompatModeActivityController(Context context, TaskStackChangeListeners listeners,
- CommandQueue commandQueue) {
- super(context);
- mCommandQueue = commandQueue;
- listeners.registerTaskStackListener(new TaskStackChangeListener() {
- @Override
- public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
- // Note the callback already runs on main thread.
- updateRestartButton(displayId, activityToken);
- }
- });
- }
-
- @Override
- public void start() {
- mCommandQueue.addCallback(this);
- }
-
- @Override
- public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
- boolean showImeSwitcher) {
- RestartActivityButton button = mActiveButtons.get(displayId);
- if (button == null) {
- return;
- }
- boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
- int newVisibility = imeShown ? View.GONE : View.VISIBLE;
- // Hide the button when input method is showing.
- if (button.getVisibility() != newVisibility) {
- button.setVisibility(newVisibility);
- }
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- mDisplayContextCache.remove(displayId);
- removeRestartButton(displayId);
- }
-
- private void removeRestartButton(int displayId) {
- RestartActivityButton button = mActiveButtons.get(displayId);
- if (button != null) {
- button.remove();
- mActiveButtons.remove(displayId);
- }
- }
-
- private void updateRestartButton(int displayId, IBinder activityToken) {
- if (activityToken == null) {
- // Null token means the current foreground activity is not in size compatibility mode.
- removeRestartButton(displayId);
- return;
- }
-
- RestartActivityButton restartButton = mActiveButtons.get(displayId);
- if (restartButton != null) {
- restartButton.updateLastTargetActivity(activityToken);
- return;
- }
-
- Context context = getOrCreateDisplayContext(displayId);
- if (context == null) {
- Log.i(TAG, "Cannot get context for display " + displayId);
- return;
- }
-
- restartButton = createRestartButton(context);
- restartButton.updateLastTargetActivity(activityToken);
- if (restartButton.show()) {
- mActiveButtons.append(displayId, restartButton);
- } else {
- onDisplayRemoved(displayId);
- }
- }
-
- @VisibleForTesting
- RestartActivityButton createRestartButton(Context context) {
- RestartActivityButton button = new RestartActivityButton(context, mHasShownHint);
- mHasShownHint = true;
- return button;
- }
-
- private Context getOrCreateDisplayContext(int displayId) {
- if (displayId == Display.DEFAULT_DISPLAY) {
- return mContext;
- }
- Context context = null;
- WeakReference<Context> ref = mDisplayContextCache.get(displayId);
- if (ref != null) {
- context = ref.get();
- }
- if (context == null) {
- Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
- if (display != null) {
- context = mContext.createDisplayContext(display);
- mDisplayContextCache.put(displayId, new WeakReference<Context>(context));
- }
- }
- return context;
- }
-
- @VisibleForTesting
- static class RestartActivityButton extends ImageButton implements View.OnClickListener,
- View.OnLongClickListener {
-
- final WindowManager.LayoutParams mWinParams;
- final boolean mShouldShowHint;
- IBinder mLastActivityToken;
-
- final int mPopupOffsetX;
- final int mPopupOffsetY;
- PopupWindow mShowingHint;
-
- RestartActivityButton(Context context, boolean hasShownHint) {
- super(context);
- mShouldShowHint = !hasShownHint;
- Drawable drawable = context.getDrawable(R.drawable.btn_restart);
- setImageDrawable(drawable);
- setContentDescription(context.getString(R.string.restart_button_description));
-
- int drawableW = drawable.getIntrinsicWidth();
- int drawableH = drawable.getIntrinsicHeight();
- mPopupOffsetX = drawableW / 2;
- mPopupOffsetY = drawableH * 2;
-
- ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
- GradientDrawable mask = new GradientDrawable();
- mask.setShape(GradientDrawable.OVAL);
- mask.setColor(color);
- setBackground(new RippleDrawable(color, null /* content */, mask));
- setOnClickListener(this);
- setOnLongClickListener(this);
-
- mWinParams = new WindowManager.LayoutParams();
- mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
- mWinParams.width = drawableW * 2;
- mWinParams.height = drawableH * 2;
- mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
- mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- mWinParams.format = PixelFormat.TRANSLUCENT;
- mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- mWinParams.setTitle(SizeCompatModeActivityController.class.getSimpleName()
- + context.getDisplayId());
- }
-
- void updateLastTargetActivity(IBinder activityToken) {
- mLastActivityToken = activityToken;
- }
-
- /** @return {@code false} if the target display is invalid. */
- boolean show() {
- try {
- getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
- } catch (WindowManager.InvalidDisplayException e) {
- // The target display may have been removed when the callback has just arrived.
- Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
- return false;
- }
- return true;
- }
-
- void remove() {
- if (mShowingHint != null) {
- mShowingHint.dismiss();
- }
- getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
- }
-
- @Override
- public void onClick(View v) {
- ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
- }
-
- @Override
- public boolean onLongClick(View v) {
- showHint();
- return true;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (mShouldShowHint) {
- showHint();
- }
- }
-
- @Override
- public void setLayoutDirection(int layoutDirection) {
- int gravity = getGravity(layoutDirection);
- if (mWinParams.gravity != gravity) {
- mWinParams.gravity = gravity;
- if (mShowingHint != null) {
- mShowingHint.dismiss();
- showHint();
- }
- getContext().getSystemService(WindowManager.class).updateViewLayout(this,
- mWinParams);
- }
- super.setLayoutDirection(layoutDirection);
- }
-
- void showHint() {
- if (mShowingHint != null) {
- return;
- }
-
- View popupView = LayoutInflater.from(getContext()).inflate(
- R.layout.size_compat_mode_hint, null /* root */);
- PopupWindow popupWindow = new PopupWindow(popupView,
- LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- popupWindow.setWindowLayoutType(mWinParams.type);
- popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
- popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
- popupWindow.setClippingEnabled(false);
- popupWindow.setOnDismissListener(() -> mShowingHint = null);
- mShowingHint = popupWindow;
-
- Button gotItButton = popupView.findViewById(R.id.got_it);
- gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
- null /* content */, null /* mask */));
- gotItButton.setOnClickListener(view -> popupWindow.dismiss());
- popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
- }
-
- private static int getGravity(int layoutDirection) {
- return Gravity.BOTTOM
- | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
new file mode 100644
index 0000000..2083f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.NonNull;
+import android.view.Surface;
+
+/**
+ * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
+ * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
+ * illumination is no longer necessary.
+ */
+public interface HbmCallback {
+ /**
+ * UdfpsView will call this to enable the HBM before drawing the illumination dot.
+ *
+ * @param surface A valid surface for which the HBM should be enabled.
+ */
+ void enableHbm(@NonNull Surface surface);
+
+ /**
+ * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
+ *
+ * @param surface A valid surface for which the HBM should be disabled.
+ */
+ void disableHbm(@NonNull Surface surface);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
index 40fe7b1..3ea8140 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
@@ -52,4 +52,18 @@
public void setAlpha(int alpha) {
mFingerprintDrawable.setAlpha(alpha);
}
+
+ /**
+ * @return The amount of padding that's needed on each side of the sensor, in pixels.
+ */
+ public int getPaddingX() {
+ return 0;
+ }
+
+ /**
+ * @return The amount of padding that's needed on each side of the sensor, in pixels.
+ */
+ public int getPaddingY() {
+ return 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
index 52662ae..e07c8403 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
@@ -28,7 +28,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -81,15 +80,19 @@
}
@Override
+ public int getPaddingX() {
+ return (int) Math.ceil(SHADOW_RADIUS);
+ }
+
+ @Override
+ public int getPaddingY() {
+ return (int) Math.ceil(SHADOW_RADIUS);
+ }
+
+ @Override
public void setAlpha(int alpha) {
super.setAlpha(alpha);
-
- // Gradually fade into the notification shade color. This needs to be done because the
- // UDFPS view is drawn on a layer on top of the notification shade
- final float percent = alpha / 255.f;
- mSensorPaint.setColor(ColorUtils.blendARGB(mNotificationShadeColor, Color.WHITE, percent));
- mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0,
- ColorUtils.blendARGB(mNotificationShadeColor, Color.BLACK, percent));
+ mSensorPaint.setAlpha(alpha);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
new file mode 100644
index 0000000..4e3419e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+/**
+ * Class that coordinates non-HBM animations (such as enroll, keyguard, BiometricPrompt,
+ * FingerprintManager).
+ */
+public class UdfpsAnimationView extends View implements DozeReceiver,
+ StatusBar.ExpansionChangedListener {
+
+ private static final String TAG = "UdfpsAnimationView";
+
+ @NonNull private UdfpsView mParent;
+ @Nullable private UdfpsAnimation mUdfpsAnimation;
+ @NonNull private RectF mSensorRect;
+ private int mAlpha;
+
+ public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mSensorRect = new RectF();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mUdfpsAnimation != null) {
+ final int alpha = mParent.shouldPauseAuth() ? mAlpha : 255;
+ mUdfpsAnimation.setAlpha(alpha);
+ mUdfpsAnimation.draw(canvas);
+ }
+ }
+
+ private int expansionToAlpha(float expansion) {
+ // Fade to 0 opacity when reaching this expansion amount
+ final float maxExpansion = 0.4f;
+
+ if (expansion >= maxExpansion) {
+ return 0; // transparent
+ }
+
+ final float percent = expansion / maxExpansion;
+ return (int) ((1 - percent) * 255);
+ }
+
+ void setParent(@NonNull UdfpsView parent) {
+ mParent = parent;
+ }
+
+ void setAnimation(@Nullable UdfpsAnimation animation) {
+ mUdfpsAnimation = animation;
+ }
+
+ void onSensorRectUpdated(@NonNull RectF sensorRect) {
+ mSensorRect = sensorRect;
+ if (mUdfpsAnimation != null) {
+ mUdfpsAnimation.onSensorRectUpdated(mSensorRect);
+ }
+ }
+
+ void updateColor() {
+ if (mUdfpsAnimation != null) {
+ mUdfpsAnimation.updateColor();
+ }
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ if (mUdfpsAnimation instanceof DozeReceiver) {
+ ((DozeReceiver) mUdfpsAnimation).dozeTimeTick();
+ }
+ }
+
+ @Override
+ public void onExpansionChanged(float expansion, boolean expanded) {
+ mAlpha = expansionToAlpha(expansion);
+ postInvalidate();
+ }
+
+ void onEnrollmentProgress(int remaining) {
+ if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
+ ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentProgress(remaining);
+ }
+ }
+
+ void onEnrollmentHelp() {
+ if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
+ ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentHelp();
+ }
+ }
+
+ public int getPaddingX() {
+ if (mUdfpsAnimation == null) {
+ return 0;
+ }
+ return mUdfpsAnimation.getPaddingX();
+ }
+
+ public int getPaddingY() {
+ if (mUdfpsAnimation == null) {
+ return 0;
+ }
+ return mUdfpsAnimation.getPaddingY();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 008e8f5..baa5973 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -45,6 +45,7 @@
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
@@ -62,7 +63,7 @@
*/
@SuppressWarnings("deprecation")
@SysUISingleton
-public class UdfpsController implements UdfpsView.HbmCallback, DozeReceiver {
+public class UdfpsController implements DozeReceiver, HbmCallback {
private static final String TAG = "UdfpsController";
private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
@@ -155,7 +156,7 @@
WindowManager windowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
- @NonNull ScrimController scrimController) {
+ @Nullable StatusBar statusBar) {
mContext = context;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
// fingerprint manager should never be null.
@@ -186,7 +187,7 @@
mView.setSensorProperties(mSensorProps);
mView.setHbmCallback(this);
- scrimController.addScrimChangedListener(mView);
+ statusBar.addExpansionChangedListener(mView);
statusBarStateController.addCallback(mView);
mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
@@ -242,12 +243,15 @@
}
}
- private WindowManager.LayoutParams computeLayoutParams() {
+ private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimation animation) {
+ final int paddingX = animation != null ? animation.getPaddingX() : 0;
+ final int paddingY = animation != null ? animation.getPaddingY() : 0;
+
// Default dimensions assume portrait mode.
- mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
- mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
- mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius;
- mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius;
+ mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius - paddingX;
+ mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingY;
+ mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius + 2 * paddingX;
+ mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius + 2 * paddingY;
Point p = new Point();
// Gets the size based on the current rotation of the display.
@@ -289,8 +293,9 @@
if (!mIsOverlayShowing) {
try {
Log.v(TAG, "showUdfpsOverlay | adding window");
- mView.setUdfpsAnimation(getUdfpsAnimationForReason(reason));
- mWindowManager.addView(mView, computeLayoutParams());
+ final UdfpsAnimation animation = getUdfpsAnimationForReason(reason);
+ mView.setUdfpsAnimation(animation);
+ mWindowManager.addView(mView, computeLayoutParams(animation));
mView.setOnTouchListener(mOnTouchListener);
mIsOverlayShowing = true;
} catch (RuntimeException e) {
@@ -374,9 +379,8 @@
// This method can be called from the UI thread.
private void onFingerDown(int x, int y, float minor, float major) {
- mView.setOnIlluminatedRunnable(
- () -> mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
- mView.startIllumination();
+ mView.startIllumination(() ->
+ mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
}
// This method can be called from the UI thread.
@@ -386,13 +390,13 @@
}
@Override
- public void enableHbm(Surface surface) {
+ public void enableHbm(@NonNull Surface surface) {
// Do nothing. This method can be implemented for devices that require the high-brightness
// mode for fingerprint illumination.
}
@Override
- public void disableHbm(Surface surface) {
+ public void disableHbm(@NonNull Surface surface) {
// Do nothing. This method can be implemented for devices that require the high-brightness
// mode for fingerprint illumination.
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
new file mode 100644
index 0000000..8bea05b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.Nullable;
+
+/**
+ * Interface that should be implemented by UI's that need to coordinate user touches,
+ * views/animations, and modules that start/stop display illumination.
+ */
+interface UdfpsIlluminator {
+ /**
+ * @param callback Invoked when HBM should be enabled or disabled.
+ */
+ void setHbmCallback(@Nullable HbmCallback callback);
+
+ /**
+ * Invoked when illumination should start.
+ * @param onIlluminatedRunnable Invoked when the display has been illuminated.
+ */
+ void startIllumination(@Nullable Runnable onIlluminatedRunnable);
+
+ /**
+ * Invoked when illumination should end.
+ */
+ void stopIllumination();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
new file mode 100644
index 0000000..97c215e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.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 com.android.systemui.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * Under-display fingerprint sensor Surface View. The surface should be used for HBM-specific things
+ * only. All other animations should be done on the other view.
+ */
+public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
+ private static final String TAG = "UdfpsSurfaceView";
+
+ /**
+ * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
+ * several abstract methods that are not used here but require implementation.
+ */
+ private interface SimpleDrawable {
+ void draw(Canvas canvas);
+ }
+
+ @NonNull private final SurfaceHolder mHolder;
+ @NonNull private final Paint mSensorPaint;
+ @NonNull private final SimpleDrawable mIlluminationDotDrawable;
+
+ @NonNull private RectF mSensorRect;
+ @Nullable private HbmCallback mHbmCallback;
+
+ public UdfpsSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mHolder = getHolder();
+ mHolder.setFormat(PixelFormat.RGBA_8888);
+
+ mSensorRect = new RectF();
+ mSensorPaint = new Paint(0 /* flags */);
+ mSensorPaint.setAntiAlias(true);
+ mSensorPaint.setARGB(255, 255, 255, 255);
+ mSensorPaint.setStyle(Paint.Style.FILL);
+
+ mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint);
+ }
+
+ @Override
+ public void setHbmCallback(@Nullable HbmCallback callback) {
+ mHbmCallback = callback;
+ }
+
+ @Override
+ public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
+ if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+ mHbmCallback.enableHbm(mHolder.getSurface());
+ }
+ drawImmediately(mIlluminationDotDrawable);
+
+ if (onIlluminatedRunnable != null) {
+ // No framework API can reliably tell when a frame reaches the panel. A timeout is the
+ // safest solution. The frame should be displayed within 3 refresh cycles, which on a
+ // 60 Hz panel equates to 50 milliseconds.
+ postDelayed(onIlluminatedRunnable, 50 /* delayMillis */);
+ }
+ }
+
+ @Override
+ public void stopIllumination() {
+ if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+ mHbmCallback.disableHbm(mHolder.getSurface());
+ }
+
+ invalidate();
+ }
+
+ void onSensorRectUpdated(@NonNull RectF sensorRect) {
+ mSensorRect = sensorRect;
+ }
+
+ /**
+ * Immediately draws the provided drawable on this SurfaceView's surface.
+ */
+ private void drawImmediately(@NonNull SimpleDrawable drawable) {
+ Canvas canvas = null;
+ try {
+ canvas = mHolder.lockCanvas();
+ drawable.draw(canvas);
+ } finally {
+ // Make sure the surface is never left in a bad state.
+ if (canvas != null) {
+ mHolder.unlockCanvasAndPost(canvas);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 4c05b88..7e378d3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -27,73 +27,34 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
/**
- * A full screen view with a configurable illumination dot and scrim.
+ * A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other
+ * animations.
*/
-public class UdfpsView extends SurfaceView implements DozeReceiver,
- StatusBarStateController.StateListener, ScrimController.ScrimChangedListener {
+public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator,
+ StatusBarStateController.StateListener, StatusBar.ExpansionChangedListener {
private static final String TAG = "UdfpsView";
- /**
- * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
- * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
- * illumination is no longer necessary.
- */
- interface HbmCallback {
- /**
- * UdfpsView will call this to enable the HBM before drawing the illumination dot.
- *
- * @param surface A valid surface for which the HBM should be enabled.
- */
- void enableHbm(@NonNull Surface surface);
-
- /**
- * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
- *
- * @param surface A valid surface for which the HBM should be disabled.
- */
- void disableHbm(@NonNull Surface surface);
- }
-
- /**
- * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
- * several abstract methods that are not used here but require implementation.
- */
- private interface SimpleDrawable {
- void draw(Canvas canvas);
- }
-
- // Radius in pixels.
- private static final float SENSOR_SHADOW_RADIUS = 2.0f;
-
private static final int DEBUG_TEXT_SIZE_PX = 32;
- @NonNull private final SurfaceHolder mHolder;
+ @NonNull private final UdfpsSurfaceView mHbmSurfaceView;
+ @NonNull private final UdfpsAnimationView mAnimationView;
@NonNull private final RectF mSensorRect;
- @NonNull private final Paint mSensorPaint;
@NonNull private final Paint mDebugTextPaint;
- @NonNull private final SimpleDrawable mIlluminationDotDrawable;
- @NonNull private final SimpleDrawable mClearSurfaceDrawable;
-
- @Nullable private UdfpsAnimation mUdfpsAnimation;
- @Nullable private HbmCallback mHbmCallback;
- @Nullable private Runnable mOnIlluminatedRunnable;
// Used to obtain the sensor location.
@NonNull private FingerprintSensorPropertiesInternal mSensorProps;
@@ -103,7 +64,6 @@
private boolean mIlluminationRequested;
private int mStatusBarState;
private boolean mNotificationShadeExpanded;
- private int mNotificationPanelAlpha;
public UdfpsView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -121,27 +81,27 @@
a.recycle();
}
- mHolder = getHolder();
- mHolder.setFormat(PixelFormat.RGBA_8888);
+ // Inflate UdfpsSurfaceView
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ mHbmSurfaceView = (UdfpsSurfaceView) inflater.inflate(R.layout.udfps_surface_view,
+ null, false);
+ addView(mHbmSurfaceView);
+ mHbmSurfaceView.setVisibility(View.INVISIBLE);
+
+ // Inflate UdfpsAnimationView
+ mAnimationView = (UdfpsAnimationView) inflater.inflate(R.layout.udfps_animation_view,
+ null, false);
+ mAnimationView.setParent(this);
+ addView(mAnimationView);
mSensorRect = new RectF();
- mSensorPaint = new Paint(0 /* flags */);
- mSensorPaint.setAntiAlias(true);
- mSensorPaint.setARGB(255, 255, 255, 255);
- mSensorPaint.setStyle(Paint.Style.FILL);
mDebugTextPaint = new Paint();
mDebugTextPaint.setAntiAlias(true);
mDebugTextPaint.setColor(Color.BLUE);
mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
- mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint);
- mClearSurfaceDrawable = canvas -> canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-
mIlluminationRequested = false;
- // SurfaceView sets this to true by default. We must set it to false to allow
- // onDraw to be called.
- setWillNotDraw(false);
}
void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
@@ -149,29 +109,17 @@
}
void setUdfpsAnimation(@Nullable UdfpsAnimation animation) {
- mUdfpsAnimation = animation;
+ mAnimationView.setAnimation(animation);
}
- /**
- * Sets a callback that can be used to enable and disable the high-brightness mode (HBM).
- */
- void setHbmCallback(@Nullable HbmCallback callback) {
- mHbmCallback = callback;
- }
-
- /**
- * Sets a runnable that will be run when the first illumination frame reaches the panel.
- * The runnable is reset to null after it is executed once.
- */
- void setOnIlluminatedRunnable(Runnable runnable) {
- mOnIlluminatedRunnable = runnable;
+ @Override
+ public void setHbmCallback(@Nullable HbmCallback callback) {
+ mHbmSurfaceView.setHbmCallback(callback);
}
@Override
public void dozeTimeTick() {
- if (mUdfpsAnimation instanceof DozeReceiver) {
- ((DozeReceiver) mUdfpsAnimation).dozeTimeTick();
- }
+ mAnimationView.dozeTimeTick();
}
@Override
@@ -185,18 +133,20 @@
}
@Override
- public void onAlphaChanged(float alpha) {
- mNotificationPanelAlpha = (int) (alpha * 255);
- postInvalidate();
+ public void onExpansionChanged(float expansion, boolean expanded) {
+ mAnimationView.onExpansionChanged(expansion, expanded);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- mSensorRect.set(0, 0, 2 * mSensorProps.sensorRadius, 2 * mSensorProps.sensorRadius);
- if (mUdfpsAnimation != null) {
- mUdfpsAnimation.onSensorRectUpdated(new RectF(mSensorRect));
- }
+ mSensorRect.set(0 + mAnimationView.getPaddingX(),
+ 0 + mAnimationView.getPaddingY(),
+ 2 * mSensorProps.sensorRadius + mAnimationView.getPaddingX(),
+ 2 * mSensorProps.sensorRadius + mAnimationView.getPaddingY());
+
+ mHbmSurfaceView.onSensorRectUpdated(new RectF(mSensorRect));
+ mAnimationView.onSensorRectUpdated(new RectF(mSensorRect));
}
@Override
@@ -205,13 +155,7 @@
Log.v(TAG, "onAttachedToWindow");
// Retrieve the colors each time, since it depends on day/night mode
- updateColor();
- }
-
- private void updateColor() {
- if (mUdfpsAnimation != null) {
- mUdfpsAnimation.updateColor();
- }
+ mAnimationView.updateColor();
}
@Override
@@ -220,25 +164,6 @@
Log.v(TAG, "onDetachedFromWindow");
}
- /**
- * Immediately draws the provided drawable on this SurfaceView's surface.
- */
- private void drawImmediately(@NonNull SimpleDrawable drawable) {
- Canvas canvas = null;
- try {
- canvas = mHolder.lockCanvas();
- drawable.draw(canvas);
- } finally {
- // Make sure the surface is never left in a bad state.
- if (canvas != null) {
- mHolder.unlockCanvasAndPost(canvas);
- }
- }
- }
-
- /**
- * This onDraw will not execute if setWillNotDraw(true) is called.
- */
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -246,11 +171,6 @@
if (!TextUtils.isEmpty(mDebugMessage)) {
canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
}
- if (mUdfpsAnimation != null) {
- final int alpha = shouldPauseAuth() ? 255 - mNotificationPanelAlpha : 255;
- mUdfpsAnimation.setAlpha(alpha);
- mUdfpsAnimation.draw(canvas);
- }
}
}
@@ -283,7 +203,7 @@
* authentication which would cause the UDFPS icons to abruptly disappear, do it here by not
* sending onFingerDown and smoothly animating away.
*/
- private boolean shouldPauseAuth() {
+ boolean shouldPauseAuth() {
return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD)
|| mStatusBarState == SHADE_LOCKED
|| mStatusBarState == FULLSCREEN_USER_SWITCHER;
@@ -293,49 +213,30 @@
return mIlluminationRequested;
}
- void startIllumination() {
+ /**
+ * @param onIlluminatedRunnable Runs when the first illumination frame reaches the panel.
+ */
+ @Override
+ public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
mIlluminationRequested = true;
-
- // Disable onDraw to prevent overriding the illumination dot with the regular UI.
- setWillNotDraw(true);
-
- if (mHbmCallback != null && mHolder.getSurface().isValid()) {
- mHbmCallback.enableHbm(mHolder.getSurface());
- }
- drawImmediately(mIlluminationDotDrawable);
-
- if (mOnIlluminatedRunnable != null) {
- // No framework API can reliably tell when a frame reaches the panel. A timeout is the
- // safest solution. The frame should be displayed within 3 refresh cycles, which on a
- // 60 Hz panel equates to 50 milliseconds.
- postDelayed(mOnIlluminatedRunnable, 50 /* delayMillis */);
- mOnIlluminatedRunnable = null;
- }
+ mAnimationView.setVisibility(View.INVISIBLE);
+ mHbmSurfaceView.setVisibility(View.VISIBLE);
+ mHbmSurfaceView.startIllumination(onIlluminatedRunnable);
}
- void stopIllumination() {
+ @Override
+ public void stopIllumination() {
mIlluminationRequested = false;
-
- if (mHbmCallback != null && mHolder.getSurface().isValid()) {
- mHbmCallback.disableHbm(mHolder.getSurface());
- }
- // It may be necessary to clear the surface for the HBM changes to apply.
- drawImmediately(mClearSurfaceDrawable);
-
- // Enable onDraw to allow the regular UI to be drawn.
- setWillNotDraw(false);
- invalidate();
+ mAnimationView.setVisibility(View.VISIBLE);
+ mHbmSurfaceView.setVisibility(View.INVISIBLE);
+ mHbmSurfaceView.stopIllumination();
}
void onEnrollmentProgress(int remaining) {
- if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
- ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentProgress(remaining);
- }
+ mAnimationView.onEnrollmentProgress(remaining);
}
void onEnrollmentHelp() {
- if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
- ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentHelp();
- }
+ mAnimationView.onEnrollmentHelp();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 55359ea..e5c9d10 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -18,7 +18,6 @@
import com.android.systemui.LatencyTester;
import com.android.systemui.ScreenDecorations;
-import com.android.systemui.SizeCompatModeActivityController;
import com.android.systemui.SliceBroadcastRelayHandler;
import com.android.systemui.SystemUI;
import com.android.systemui.accessibility.SystemActions;
@@ -113,13 +112,6 @@
@ClassKey(ShortcutKeyDispatcher.class)
public abstract SystemUI bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui);
- /** Inject into SizeCompatModeActivityController. */
- @Binds
- @IntoMap
- @ClassKey(SizeCompatModeActivityController.class)
- public abstract SystemUI bindsSizeCompatModeActivityController(
- SizeCompatModeActivityController sysui);
-
/** Inject into SliceBroadcastRelayHandler. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index fcb5da3..dab4d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -545,6 +545,7 @@
}
mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+ mNavigationBarView.setBehavior(mBehavior);
mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener);
@@ -665,7 +666,7 @@
}
private void initSecondaryHomeHandleForRotation() {
- if (!canShowSecondaryHandle()) {
+ if (mNavBarMode != NAV_BAR_MODE_GESTURAL) {
return;
}
@@ -919,6 +920,9 @@
}
if (mBehavior != behavior) {
mBehavior = behavior;
+ if (mNavigationBarView != null) {
+ mNavigationBarView.setBehavior(behavior);
+ }
updateSystemUiStateFlags(-1);
}
}
@@ -1520,7 +1524,7 @@
}
private boolean canShowSecondaryHandle() {
- return mNavBarMode == NAV_BAR_MODE_GESTURAL;
+ return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
}
private final Consumer<Integer> mRotationWatcher = rotation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index e7f2b222..c07404c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -57,6 +57,7 @@
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsets;
+import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -626,6 +627,10 @@
mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
}
+ public void setBehavior(@Behavior int behavior) {
+ mRotationButtonController.onBehaviorChanged(behavior);
+ }
+
@Override
public void setLayoutDirection(int layoutDirection) {
reloadNavIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index df9e7a4..33d1807 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -35,6 +35,8 @@
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowInsetsController.Behavior;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -78,6 +80,7 @@
private Consumer<Integer> mRotWatcherListener;
private boolean mListenersRegistered = false;
private boolean mIsNavigationBarShowing;
+ private @Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
private boolean mSkipOverrideUserLockPrefsOnce;
private int mLightIconColor;
private int mDarkIconColor;
@@ -297,8 +300,8 @@
}
mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
- if (mIsNavigationBarShowing) {
- // The navbar is visible so show the icon right away
+ if (canShowRotationButton()) {
+ // The navbar is visible / it's in visual immersive mode, so show the icon right away
showAndLogRotationSuggestion();
} else {
// If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
@@ -318,14 +321,28 @@
void onNavigationBarWindowVisibilityChange(boolean showing) {
if (mIsNavigationBarShowing != showing) {
mIsNavigationBarShowing = showing;
-
- // If the navbar is visible, show the rotate button if there's a pending suggestion
- if (showing && mPendingRotationSuggestion) {
- showAndLogRotationSuggestion();
- }
+ showPendingRotationButtonIfNeeded();
}
}
+ void onBehaviorChanged(@Behavior int behavior) {
+ if (mBehavior != behavior) {
+ mBehavior = behavior;
+ showPendingRotationButtonIfNeeded();
+ }
+ }
+
+ private void showPendingRotationButtonIfNeeded() {
+ if (canShowRotationButton() && mPendingRotationSuggestion) {
+ showAndLogRotationSuggestion();
+ }
+ }
+
+ /** Return true when either the nav bar is visible or it's in visual immersive mode. */
+ private boolean canShowRotationButton() {
+ return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
+ }
+
public Context getContext() {
return mContext;
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 66c535f..3f79be8 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -24,7 +24,6 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowInsets
import android.widget.ImageView
@@ -66,9 +65,8 @@
super.onCreate(savedInstanceState)
window?.apply {
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
- setLayout(MATCH_PARENT, WRAP_CONTENT)
+ setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
- setBackgroundDrawable(null)
}
setContentView(R.layout.privacy_dialog)
@@ -125,12 +123,12 @@
val finalText = element.attribution?.let {
TextUtils.concat(
firstLine,
- "\n",
+ " ",
context.getString(R.string.ongoing_privacy_dialog_attribution_text, it)
)
} ?: firstLine
newView.requireViewById<TextView>(R.id.text).text = finalText
- newView.requireViewById<View>(R.id.link).apply {
+ newView.apply {
tag = element.type.permGroupName
setOnClickListener(clickListener)
}
@@ -154,9 +152,7 @@
}
private val clickListener = View.OnClickListener { v ->
- if (v.id == R.id.link) {
- v.tag?.let { activityStarter(it as String) }
- }
+ v.tag?.let { activityStarter(it as String) }
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index 99e9bfc..8b6c04f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -227,21 +227,20 @@
/**
* Filters the list of elements to show.
*
- * * Return at most one element per [PrivacyType], sorted by the natural order of the
- * [PrivacyType].
- * * If there are no active usages for a type, return the most recent
- * * If there are multiple active usages for a type, return the most active recent.
+ * For each privacy type, it'll return all active elements. If there are no active elements,
+ * it'll return the most recent access
*/
private fun filterAndSelect(
list: List<PrivacyDialog.PrivacyElement>
): List<PrivacyDialog.PrivacyElement> {
- return list.groupBy { it.type }.toSortedMap().mapNotNull { entry ->
- if (entry.value.isEmpty()) {
- null
+ return list.groupBy { it.type }.toSortedMap().flatMap { (_, elements) ->
+ val actives = elements.filter { it.active }
+ if (actives.isNotEmpty()) {
+ actives.sortedByDescending { it.lastActiveTimestamp }
} else {
- val actives = entry.value.filter { it.active }
- val out = if (actives.isNotEmpty()) actives else entry.value
- out.maxByOrNull { it.lastActiveTimestamp }
+ elements.maxByOrNull { it.lastActiveTimestamp }?.let {
+ listOf(it)
+ } ?: emptyList()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index e9207f1..d248ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -29,6 +29,8 @@
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.tileimpl.QSTileBaseView;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -87,11 +89,13 @@
private final Executor mExecutor;
private final TunerService mTunerService;
private boolean mShowCollapsedOnKeyguard;
+ private final FeatureFlags mFeatureFlags;
@Inject
public QSAnimator(QS qs, QuickQSPanel quickPanel, QSPanelController qsPanelController,
QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
- QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService) {
+ QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
+ FeatureFlags featureFlags) {
mQs = qs;
mQuickQsPanel = quickPanel;
mQsPanelController = qsPanelController;
@@ -100,6 +104,7 @@
mHost = qsTileHost;
mExecutor = executor;
mTunerService = tunerService;
+ mFeatureFlags = featureFlags;
mHost.addCallback(this);
mQsPanelController.addOnAttachStateChangeListener(this);
qs.getView().addOnLayoutChangeListener(this);
@@ -228,6 +233,7 @@
// Quick tiles.
QSTileView quickTileView = mQuickQSPanelController.getTileView(tile);
if (quickTileView == null) continue;
+ View qqsBgCircle = ((QSTileBaseView) quickTileView).getBgCircle();
getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view);
getRelativePosition(loc2, tileIcon, view);
@@ -249,6 +255,11 @@
translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0);
+ if (mFeatureFlags.isQSLabelsEnabled()) {
+ firstPageBuilder.addFloat(qqsBgCircle, "alpha", 1, 1, 0);
+ mAllViews.add(qqsBgCircle);
+ }
+
} else { // These tiles disappear when expanding
firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0);
translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index d0601f0..91ae571 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -777,10 +777,6 @@
updatePadding();
}
- boolean useSideLabels() {
- return mSideLabels;
- }
-
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 30774be..f56a890 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -19,7 +19,6 @@
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_SIDE_LABELS;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import android.annotation.NonNull;
@@ -93,8 +92,7 @@
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSlider.Factory brightnessSliderFactory,
- @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
- @Named(QS_SIDE_LABELS) boolean useSideLabels) {
+ @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
mQsSecurityFooter = qsSecurityFooter;
@@ -110,7 +108,7 @@
mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider);
mQsLabelsFlag = qsLabelsFlag;
- mSideLabels = useSideLabels;
+ mSideLabels = qsLabelsFlag;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index e2d7d20..a0db200 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
+import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import com.android.internal.logging.MetricsLogger;
@@ -40,6 +41,8 @@
@QSScope
public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
+ private boolean mUseSideLabels;
+
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
newConfig -> {
int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
@@ -54,9 +57,10 @@
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager) {
+ DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
+ mUseSideLabels = qsLabelsFlag;
}
@Override
@@ -97,7 +101,7 @@
break;
}
}
- if (mView.useSideLabels()) {
+ if (mUseSideLabels) {
List<QSTile> newTiles = new ArrayList<>();
for (int i = 0; i < tiles.size(); i += 2) {
newTiles.add(tiles.get(i));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
index ad4c8bb..9ab2d73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Named;
@@ -28,7 +27,6 @@
@Module
public interface QSFlagsModule {
String QS_LABELS_FLAG = "qs_labels_flag";
- String QS_SIDE_LABELS = "qs_side_labels";
@Provides
@SysUISingleton
@@ -36,12 +34,4 @@
static boolean provideQSFlag(FeatureFlags featureFlags) {
return featureFlags.isQSLabelsEnabled();
}
-
- @Provides
- @SysUISingleton
- @Named(QS_SIDE_LABELS)
- static boolean provideSideLabels(SecureSettings secureSettings,
- @Named(QS_LABELS_FLAG) boolean qsLabels) {
- return qsLabels && secureSettings.getInt("sysui_side_labels", 0) != 0;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 9e582dd..11e6330 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -14,7 +14,7 @@
package com.android.systemui.qs.tileimpl;
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_SIDE_LABELS;
+import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
import android.content.Context;
import android.os.Build;
@@ -98,7 +98,7 @@
@Inject
public QSFactoryImpl(
Lazy<QSHost> qsHostLazy,
- @Named(QS_SIDE_LABELS) boolean useSideLabels,
+ @Named(QS_LABELS_FLAG) boolean useSideLabels,
Provider<CustomTile.Builder> customTileBuilderProvider,
Provider<WifiTile> wifiTileProvider,
Provider<InternetTile> internetTileProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 2dbd2cf..a699e2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -36,7 +36,7 @@
/** View that represents a standard quick settings tile. **/
public class QSTileView extends QSTileBaseView {
- private static final int MAX_LABEL_LINES = 2;
+ protected int mMaxLabelLines = 2;
private View mDivider;
protected TextView mLabel;
protected TextView mSecondLine;
@@ -109,10 +109,17 @@
// Remeasure view if the primary label requires more then 2 lines or the secondary label
// text will be cut off.
- if (mLabel.getLineCount() > MAX_LABEL_LINES || !TextUtils.isEmpty(mSecondLine.getText())
+ if (mLabel.getLineCount() > mMaxLabelLines || !TextUtils.isEmpty(mSecondLine.getText())
&& mSecondLine.getLineHeight() > mSecondLine.getHeight()) {
- mLabel.setSingleLine();
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (!mLabel.isSingleLine()) {
+ mLabel.setSingleLine();
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ } else {
+ if (mLabel.isSingleLine()) {
+ mLabel.setSingleLine(false);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
index 2ef78c2..dc81b702 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
@@ -24,7 +24,6 @@
import android.graphics.drawable.RippleDrawable
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.view.Gravity
-import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import com.android.systemui.R
@@ -42,38 +41,23 @@
init {
orientation = HORIZONTAL
- mDualTargetAllowed = true
+ mDualTargetAllowed = false
mBg.setImageDrawable(null)
- createDivider()
mColorLabelActive = ColorStateList.valueOf(getColorForState(getContext(), STATE_ACTIVE))
+ mMaxLabelLines = 3
}
override fun createLabel() {
super.createLabel()
findViewById<LinearLayout>(R.id.label_group)?.gravity = Gravity.START
mLabel.gravity = Gravity.START
+ mLabel.textDirection = TEXT_DIRECTION_LOCALE
mSecondLine.gravity = Gravity.START
+ mSecondLine.textDirection = TEXT_DIRECTION_LOCALE
val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding)
- mLabelContainer.setPadding(padding, padding, padding, padding)
- (mLabelContainer.layoutParams as LayoutParams).gravity = Gravity.CENTER_VERTICAL
- }
-
- fun createDivider() {
- divider = LayoutInflater.from(context).inflate(R.layout.qs_tile_label_divider, this, false)
- val position = indexOfChild(mLabelContainer)
- addView(divider, position)
- }
-
- override fun init(
- click: OnClickListener?,
- secondaryClick: OnClickListener?,
- longClick: OnLongClickListener?
- ) {
- super.init(click, secondaryClick, longClick)
- mLabelContainer.setOnClickListener {
- longClick?.onLongClick(it)
- }
- mLabelContainer.isClickable = false
+ mLabelContainer.setPaddingRelative(0, padding, padding, padding)
+ (mLabelContainer.layoutParams as LayoutParams).gravity =
+ Gravity.CENTER_VERTICAL or Gravity.START
}
override fun updateRippleSize() {
@@ -83,7 +67,7 @@
val d = super.newTileBackground()
if (paintDrawable == null) {
paintDrawable = PaintDrawable(Color.WHITE).apply {
- setCornerRadius(30f)
+ setCornerRadius(50f)
}
}
if (d is RippleDrawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index b6e07b1..3841dac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -36,6 +36,7 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
@@ -49,9 +50,10 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- IndividualSensorPrivacyController sensorPrivacyController) {
+ IndividualSensorPrivacyController sensorPrivacyController,
+ KeyguardStateController keyguardStateController) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
- activityStarter, qsLogger, sensorPrivacyController);
+ activityStarter, qsLogger, sensorPrivacyController, keyguardStateController);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 1b2ad4c..191b85b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -54,6 +54,9 @@
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.WifiIcons;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
import javax.inject.Inject;
/** Quick settings tile: Internet **/
@@ -65,7 +68,7 @@
protected final NetworkController mController;
private final DataUsageController mDataController;
private final QSTile.SignalState mStateBeforeClick = newTileState();
- // The last updated tile state, 0: mobile, 1: wifi
+ // The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
private int mLastTileState = -1;
protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();
@@ -140,6 +143,21 @@
return string;
}
+ private static final class EthernetCallbackInfo {
+ boolean mConnected;
+ int mEthernetSignalIconId;
+ String mEthernetContentDescription;
+
+ @Override
+ public String toString() {
+ return new StringBuilder("EthernetCallbackInfo[")
+ .append("mConnected=").append(mConnected)
+ .append(",mEthernetSignalIconId=").append(mEthernetSignalIconId)
+ .append(",mEthernetContentDescription=").append(mEthernetContentDescription)
+ .append(']').toString();
+ }
+ }
+
private static final class WifiCallbackInfo {
boolean mAirplaneModeEnabled;
boolean mEnabled;
@@ -212,6 +230,8 @@
protected final class InternetSignalCallback implements SignalCallback {
final WifiCallbackInfo mWifiInfo = new WifiCallbackInfo();
final CellularCallbackInfo mCellularInfo = new CellularCallbackInfo();
+ final EthernetCallbackInfo mEthernetInfo = new EthernetCallbackInfo();
+
@Override
public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
@@ -230,14 +250,12 @@
}
// When airplane mode is enabled, we need to refresh the Internet Tile even if the WiFi
// is not the default network.
- if (qsIcon == null && !mWifiInfo.mAirplaneModeEnabled) {
+ if (qsIcon == null) {
return;
}
- if (qsIcon != null) {
- mWifiInfo.mConnected = qsIcon.visible;
- mWifiInfo.mWifiSignalIconId = qsIcon.icon;
- mWifiInfo.mWifiSignalContentDescription = qsIcon.contentDescription;
- }
+ mWifiInfo.mConnected = qsIcon.visible;
+ mWifiInfo.mWifiSignalIconId = qsIcon.icon;
+ mWifiInfo.mWifiSignalContentDescription = qsIcon.contentDescription;
mWifiInfo.mEnabled = enabled;
mWifiInfo.mSsid = description;
mWifiInfo.mActivityIn = activityIn;
@@ -287,6 +305,20 @@
}
@Override
+ public void setEthernetIndicators(IconState icon) {
+ if (DEBUG) {
+ Log.d(TAG, "setEthernetIndicators: "
+ + "icon = " + (icon == null ? "" : icon.toString()));
+ }
+ mEthernetInfo.mConnected = icon.visible;
+ mEthernetInfo.mEthernetSignalIconId = icon.icon;
+ mEthernetInfo.mEthernetContentDescription = icon.contentDescription;
+ if (icon.visible) {
+ refreshState(mEthernetInfo);
+ }
+ }
+
+ @Override
public void setNoSims(boolean show, boolean simDetected) {
if (DEBUG) {
Log.d(TAG, "setNoSims: "
@@ -299,7 +331,6 @@
mCellularInfo.mMobileSignalIconId = 0;
mCellularInfo.mQsTypeIcon = 0;
}
- refreshState(mCellularInfo);
}
@Override
@@ -310,7 +341,9 @@
}
mCellularInfo.mAirplaneModeEnabled = icon.visible;
mWifiInfo.mAirplaneModeEnabled = icon.visible;
- refreshState(mCellularInfo);
+ if (!mSignalCallback.mEthernetInfo.mConnected) {
+ refreshState(mCellularInfo);
+ }
}
@Override
@@ -330,6 +363,15 @@
mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
refreshState(mWifiInfo);
}
+
+ @Override
+ public String toString() {
+ return new StringBuilder("InternetSignalCallback[")
+ .append("mWifiInfo=").append(mWifiInfo)
+ .append(",mCellularInfo=").append(mCellularInfo)
+ .append(",mEthernetInfo=").append(mEthernetInfo)
+ .append(']').toString();
+ }
}
@Override
@@ -340,6 +382,9 @@
} else if (arg instanceof WifiCallbackInfo) {
mLastTileState = 1;
handleUpdateWifiState(state, arg);
+ } else if (arg instanceof EthernetCallbackInfo) {
+ mLastTileState = 2;
+ handleUpdateEthernetState(state, arg);
} else {
// handleUpdateState will be triggered when user expands the QuickSetting panel with
// arg = null, in this case the last updated CellularCallbackInfo or WifiCallbackInfo
@@ -348,6 +393,8 @@
handleUpdateCellularState(state, mSignalCallback.mCellularInfo);
} else if (mLastTileState == 1) {
handleUpdateWifiState(state, mSignalCallback.mWifiInfo);
+ } else if (mLastTileState == 2) {
+ handleUpdateEthernetState(state, mSignalCallback.mEthernetInfo);
}
}
}
@@ -440,7 +487,6 @@
Log.d(TAG, "handleUpdateCellularState: " + "CellularCallbackInfo = " + cb.toString());
}
final Resources r = mContext.getResources();
- // TODO(b/174753536): Use the new "Internet" string as state.label once available.
state.label = r.getString(R.string.quick_settings_internet_label);
state.state = Tile.STATE_ACTIVE;
boolean mobileDataEnabled = mDataController.isMobileDataSupported()
@@ -478,6 +524,18 @@
}
}
+ private void handleUpdateEthernetState(SignalState state, Object arg) {
+ EthernetCallbackInfo cb = (EthernetCallbackInfo) arg;
+ if (DEBUG) {
+ Log.d(TAG, "handleUpdateEthernetState: " + "EthernetCallbackInfo = " + cb.toString());
+ }
+ final Resources r = mContext.getResources();
+ state.label = r.getString(R.string.quick_settings_internet_label);
+ state.state = Tile.STATE_ACTIVE;
+ state.icon = ResourceIcon.get(cb.mEthernetSignalIconId);
+ state.secondaryLabel = cb.mEthernetContentDescription;
+ }
+
private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) {
if (TextUtils.isEmpty(dataType)) {
return Html.fromHtml((current == null ? "" : current.toString()), 0);
@@ -519,4 +577,15 @@
return d;
}
}
+
+ /**
+ * Dumps the state of this tile along with its name.
+ */
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(this.getClass().getSimpleName() + ":");
+ pw.print(" "); pw.println(getState().toString());
+ pw.print(" "); pw.println("mLastTileState=" + mLastTileState);
+ pw.print(" "); pw.println("mSignalCallback=" + mSignalCallback.toString());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index 9cc6f09..2f0071a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -36,6 +36,7 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
@@ -49,9 +50,10 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- IndividualSensorPrivacyController sensorPrivacyController) {
+ IndividualSensorPrivacyController sensorPrivacyController,
+ KeyguardStateController keyguardStateController) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
- activityStarter, qsLogger, sensorPrivacyController);
+ activityStarter, qsLogger, sensorPrivacyController, keyguardStateController);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 12205d6..00703e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -35,6 +35,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
/**
* Superclass to toggle individual sensor privacy via quick settings tiles
@@ -42,6 +43,7 @@
public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanState> implements
IndividualSensorPrivacyController.Callback {
+ private final KeyguardStateController mKeyguard;
private IndividualSensorPrivacyController mSensorPrivacyController;
/**
@@ -61,10 +63,12 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- IndividualSensorPrivacyController sensorPrivacyController) {
+ IndividualSensorPrivacyController sensorPrivacyController,
+ KeyguardStateController keyguardStateController) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mSensorPrivacyController = sensorPrivacyController;
+ mKeyguard = keyguardStateController;
mSensorPrivacyController.observe(getLifecycle(), this);
}
@@ -75,6 +79,13 @@
@Override
protected void handleClick() {
+ if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+ mSensorPrivacyController.setSensorBlocked(getSensorId(),
+ !mSensorPrivacyController.isSensorBlocked(getSensorId()));
+ });
+ return;
+ }
mSensorPrivacyController.setSensorBlocked(getSensorId(),
!mSensorPrivacyController.isSensorBlocked(getSensorId()));
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 760ebad..a9f76f6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -35,6 +35,7 @@
import android.annotation.FloatRange;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -83,6 +84,7 @@
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.ISplitScreenListener;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -98,6 +100,7 @@
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.transition.Transitions;
import java.io.FileDescriptor;
@@ -133,7 +136,8 @@
private final Context mContext;
private final Optional<Pip> mPipOptional;
private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
- private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<SplitScreen> mSplitScreenOptional;
private SysUiState mSysUiState;
private final Handler mHandler;
private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -263,7 +267,7 @@
}
final long token = Binder.clearCallingIdentity();
try {
- return mSplitScreenOptional.map(splitScreen ->
+ return mLegacySplitScreenOptional.map(splitScreen ->
splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
.orElse(null);
} finally {
@@ -401,7 +405,7 @@
@Override
public void setSplitScreenMinimized(boolean minimized) {
- mSplitScreenOptional.ifPresent(
+ mLegacySplitScreenOptional.ifPresent(
splitScreen -> splitScreen.setMinimized(minimized));
}
@@ -559,6 +563,105 @@
}
}
+ @Override
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ if (!verifyCaller("registerSplitScreenListener")) {
+ return;
+ }
+ mISplitScreenListener = listener;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(
+ s -> s.registerSplitScreenListener(mSplitScreenListener));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ if (!verifyCaller("unregisterSplitScreenListener")) {
+ return;
+ }
+ mISplitScreenListener = null;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(
+ s -> s.unregisterSplitScreenListener(mSplitScreenListener));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setSideStageVisibility(boolean visible) {
+ if (!verifyCaller("setSideStageVisibility")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(s -> s.setSideStageVisibility(visible));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void exitSplitScreen() {
+ if (!verifyCaller("exitSplitScreen")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(s -> s.exitSplitScreen());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void startTask(int taskId, int stage, int position, Bundle options) {
+ if (!verifyCaller("startTask")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(
+ s -> s.startTask(taskId, stage, position, options));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ Bundle options, UserHandle user) {
+ if (!verifyCaller("startShortcut")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(s ->
+ s.startShortcut(packageName, shortcutId, stage, position, options, user));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void startIntent(PendingIntent intent, int stage, int position, Bundle options) {
+ if (!verifyCaller("startIntent")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mSplitScreenOptional.ifPresent(s ->
+ s.startIntent(intent, stage, position, options));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -658,6 +761,32 @@
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
= this::cleanupAfterDeath;
+ private ISplitScreenListener mISplitScreenListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mISplitScreenListener != null) {
+ mISplitScreenListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "onStagePositionChanged", e);
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage) {
+ try {
+ if (mISplitScreenListener != null) {
+ mISplitScreenListener.onTaskStageChanged(taskId, stage);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "onTaskStageChanged", e);
+ }
+ }
+ };
+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyService(Context context, CommandQueue commandQueue,
@@ -665,7 +794,8 @@
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
Optional<Pip> pipOptional,
- Optional<LegacySplitScreen> splitScreenOptional,
+ Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<SplitScreen> splitScreenOptional,
Optional<Lazy<StatusBar>> statusBarOptionalLazy,
Optional<OneHanded> oneHandedOptional,
BroadcastDispatcher broadcastDispatcher,
@@ -718,9 +848,10 @@
});
mCommandQueue = commandQueue;
- splitScreenOptional.ifPresent(splitScreen ->
- splitScreen.registerBoundsChangeListener(mSplitScreenBoundsChangeListener));
mSplitScreenOptional = splitScreenOptional;
+ legacySplitScreenOptional.ifPresent(splitScreen ->
+ splitScreen.registerBoundsChangeListener(mSplitScreenBoundsChangeListener));
+ mLegacySplitScreenOptional = legacySplitScreenOptional;
// Listen for user setup
startTracking();
@@ -835,7 +966,7 @@
startConnectionToCurrentUser();
// Clean up the minimized state if launcher dies
- mSplitScreenOptional.ifPresent(
+ mLegacySplitScreenOptional.ifPresent(
splitScreen -> splitScreen.setMinimized(false));
// Clean up any registered remote transitions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
new file mode 100644
index 0000000..8e182b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -0,0 +1,151 @@
+/*
+ * 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.screenshot;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * CropView has top and bottom draggable crop handles, with a scrim to darken the areas being
+ * cropped out.
+ */
+public class CropView extends View {
+ private enum CropBoundary {
+ NONE, TOP, BOTTOM
+ }
+
+ private final float mCropTouchMargin;
+ private final Paint mShadePaint;
+ private final Paint mHandlePaint;
+
+ // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the
+ // view, respectively.
+ private float mTopCrop = 0f;
+ private float mBottomCrop = 1f;
+
+ private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
+ private float mLastY;
+
+ public CropView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ TypedArray t = context.getTheme().obtainStyledAttributes(
+ attrs, R.styleable.CropView, 0, 0);
+ mShadePaint = new Paint();
+ mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT));
+ mHandlePaint = new Paint();
+ mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK));
+ mHandlePaint.setStrokeWidth(
+ t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20));
+ t.recycle();
+ // 48 dp touchable region around each handle.
+ mCropTouchMargin = 24 * getResources().getDisplayMetrics().density;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ drawShade(canvas, 0, mTopCrop);
+ drawShade(canvas, mBottomCrop, 1f);
+ drawHandle(canvas, mTopCrop);
+ drawHandle(canvas, mBottomCrop);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int topPx = fractionToPixels(mTopCrop);
+ int bottomPx = fractionToPixels(mBottomCrop);
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx);
+ if (mCurrentDraggingBoundary != CropBoundary.NONE) {
+ mLastY = event.getY();
+ }
+ return true;
+ }
+ if (event.getAction() == MotionEvent.ACTION_MOVE
+ && mCurrentDraggingBoundary != CropBoundary.NONE) {
+ float delta = event.getY() - mLastY;
+ if (mCurrentDraggingBoundary == CropBoundary.TOP) {
+ mTopCrop = pixelsToFraction((int) MathUtils.constrain(topPx + delta, 0,
+ bottomPx - 2 * mCropTouchMargin));
+ } else { // Bottom
+ mBottomCrop = pixelsToFraction((int) MathUtils.constrain(bottomPx + delta,
+ topPx + 2 * mCropTouchMargin, getMeasuredHeight()));
+ }
+ mLastY = event.getY();
+ invalidate();
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ /**
+ * @return value [0,1] representing the position of the top crop boundary.
+ */
+ public float getTopBoundary() {
+ return mTopCrop;
+ }
+
+ /**
+ * @return value [0,1] representing the position of the bottom crop boundary.
+ */
+ public float getBottomBoundary() {
+ return mBottomCrop;
+ }
+
+ private void drawShade(Canvas canvas, float fracStart, float fracEnd) {
+ canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(),
+ fractionToPixels(fracEnd), mShadePaint);
+ }
+
+ private void drawHandle(Canvas canvas, float frac) {
+ int y = fractionToPixels(frac);
+ canvas.drawLine(0, y, getMeasuredWidth(), y, mHandlePaint);
+ }
+
+ private int fractionToPixels(float frac) {
+ return (int) (frac * getMeasuredHeight());
+ }
+
+ private float pixelsToFraction(int px) {
+ return px / (float) getMeasuredHeight();
+ }
+
+ private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) {
+ if (Math.abs(event.getY() - topPx) < mCropTouchMargin) {
+ return CropBoundary.TOP;
+ }
+ if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) {
+ return CropBoundary.BOTTOM;
+ }
+ return CropBoundary.NONE;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 8ff66f5..20f8451 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -108,6 +108,15 @@
}
Bitmap toBitmap() {
+ return toBitmap(new Rect(0, 0, getWidth(), getHeight()));
+ }
+
+ /**
+ * @param bounds Selected portion of the tile set's bounds (equivalent to tile bounds coord
+ * space). For example, to get the whole doc, use Rect(0, 0, getWidth(),
+ * getHeight()).
+ */
+ Bitmap toBitmap(Rect bounds) {
if (mTiles.isEmpty()) {
return null;
}
@@ -115,6 +124,9 @@
output.setPosition(0, 0, getWidth(), getHeight());
RecordingCanvas canvas = output.beginRecording();
canvas.translate(-getLeft(), -getTop());
+ // Additional translation to account for the requested bounds
+ canvas.translate(-bounds.left, -bounds.top);
+ canvas.clipRect(bounds);
for (ImageTile tile : mTiles) {
canvas.save();
canvas.translate(tile.getLeft(), tile.getTop());
@@ -122,7 +134,7 @@
canvas.restore();
}
output.endRecording();
- return HardwareRenderer.createHardwareBitmap(output, getWidth(), getHeight());
+ return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
}
int getLeft() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 0a7eea4..953b40b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -388,12 +388,6 @@
}
});
- // ignore system bar insets for the purpose of window layout
- mScreenshotView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets(
- new WindowInsets.Builder(insets)
- .setInsets(WindowInsets.Type.all(), Insets.NONE)
- .build()));
-
// TODO(159460485): Remove this when focus is handled properly in the system
mScreenshotView.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
@@ -533,9 +527,6 @@
attachWindow();
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
- }
mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -549,7 +540,13 @@
}
});
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setContentView: " + mScreenshotView);
+ }
setContentView(mScreenshotView);
+ // ignore system bar insets for the purpose of window layout
+ mWindow.getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> WindowInsets.CONSUMED);
cancelTimeout(); // restarted after animation
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 3bc5ebf..5c650ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -325,8 +325,6 @@
Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
}
- Rect previewBounds = new Rect();
- mScreenshotPreview.getBoundsOnScreen(previewBounds);
Rect targetPosition = new Rect();
mScreenshotPreview.getHitRect(targetPosition);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 176a2c7..18c379a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -18,10 +18,13 @@
import android.annotation.IdRes;
import android.annotation.UiThread;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver.InternalInsetsInfo;
@@ -49,6 +52,13 @@
public class ScrollCaptureController implements OnComputeInternalInsetsListener {
private static final String TAG = "ScrollCaptureController";
+ // TODO: Support saving without additional action.
+ private enum PendingAction {
+ SHARE,
+ EDIT,
+ SAVE
+ }
+
public static final int MAX_PAGES = 5;
public static final int MAX_HEIGHT = 12000;
@@ -66,12 +76,11 @@
private RequestCallback mCallback;
private Window mWindow;
private ImageView mPreview;
- private View mClose;
+ private View mSave;
+ private View mCancel;
private View mEdit;
private View mShare;
-
- private ListenableFuture<ImageExporter.Result> mExportFuture;
- private Runnable mPendingAction;
+ private CropView mCropView;
public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) {
@@ -106,11 +115,14 @@
.addOnComputeInternalInsetsListener(this);
mPreview = findViewById(R.id.preview);
- mClose = findViewById(R.id.close);
+ mSave = findViewById(R.id.save);
+ mCancel = findViewById(R.id.cancel);
mEdit = findViewById(R.id.edit);
mShare = findViewById(R.id.share);
+ mCropView = findViewById(R.id.crop_view);
- mClose.setOnClickListener(this::onClicked);
+ mSave.setOnClickListener(this::onClicked);
+ mCancel.setOnClickListener(this::onClicked);
mEdit.setOnClickListener(this::onClicked);
mShare.setOnClickListener(this::onClicked);
@@ -125,7 +137,8 @@
}
void disableButtons() {
- mClose.setEnabled(false);
+ mSave.setEnabled(false);
+ mCancel.setEnabled(false);
mEdit.setEnabled(false);
mShare.setEnabled(false);
}
@@ -134,28 +147,18 @@
Log.d(TAG, "button clicked!");
int id = v.getId();
- if (id == R.id.close) {
- v.setPressed(true);
- disableButtons();
- finish();
+ v.setPressed(true);
+ disableButtons();
+ if (id == R.id.save) {
+ startExport(PendingAction.SAVE);
+ } else if (id == R.id.cancel) {
+ doFinish();
} else if (id == R.id.edit) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
- v.setPressed(true);
- disableButtons();
- edit();
+ startExport(PendingAction.EDIT);
} else if (id == R.id.share) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
- v.setPressed(true);
- disableButtons();
- share();
- }
- }
-
- private void finish() {
- if (mExportFuture == null) {
- doFinish();
- } else {
- mExportFuture.addListener(this::doFinish, mUiExecutor);
+ startExport(PendingAction.SHARE);
}
}
@@ -167,30 +170,55 @@
.removeOnComputeInternalInsetsListener(this);
}
- private void edit() {
- sendIntentWhenReady(Intent.ACTION_EDIT);
- }
-
- private void share() {
- sendIntentWhenReady(Intent.ACTION_SEND);
- }
-
- void sendIntentWhenReady(String action) {
- if (mExportFuture != null) {
- mExportFuture.addListener(() -> {
- try {
- ImageExporter.Result result = mExportFuture.get();
- sendIntent(action, result.uri);
- mCallback.onFinish();
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "failed to export", e);
- mCallback.onFinish();
+ private void startExport(PendingAction action) {
+ Rect croppedPortion = new Rect(
+ 0,
+ (int) (mImageTileSet.getHeight() * mCropView.getTopBoundary()),
+ mImageTileSet.getWidth(),
+ (int) (mImageTileSet.getHeight() * mCropView.getBottomBoundary()));
+ ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
+ mBgExecutor, mRequestId, mImageTileSet.toBitmap(croppedPortion), mCaptureTime);
+ exportFuture.addListener(() -> {
+ try {
+ ImageExporter.Result result = exportFuture.get();
+ if (action == PendingAction.EDIT) {
+ doEdit(result.uri);
+ } else if (action == PendingAction.SHARE) {
+ doShare(result.uri);
}
+ doFinish();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "failed to export", e);
+ mCallback.onFinish();
+ }
+ }, mUiExecutor);
+ }
- }, mUiExecutor);
- } else {
- mPendingAction = this::edit;
+ private void doEdit(Uri uri) {
+ String editorPackage = mContext.getString(R.string.config_screenshotEditor);
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ intent.setComponent(ComponentName.unflattenFromString(editorPackage));
}
+ intent.setType("image/png");
+ intent.setData(uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+
+ private void doShare(Uri uri) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("image/png");
+ intent.setData(uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Intent sharingChooserIntent = Intent.createChooser(intent, null)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ mContext.startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
}
private void setContentView(@IdRes int id) {
@@ -245,21 +273,6 @@
session.end(mCallback::onFinish);
} else {
mPreview.setImageDrawable(mImageTileSet.getDrawable());
- mExportFuture = mImageExporter.export(
- mBgExecutor, mRequestId, mImageTileSet.toBitmap(), mCaptureTime);
- // The user chose an action already, link it to the result
- if (mPendingAction != null) {
- mExportFuture.addListener(mPendingAction, mUiExecutor);
- }
}
}
-
- void sendIntent(String action, Uri uri) {
- Intent editIntent = new Intent(action);
- editIntent.setType("image/png");
- editIntent.setData(uri);
- editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(editIntent, UserHandle.CURRENT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index c1161f1..d707bca0 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -17,14 +17,19 @@
package com.android.systemui.sensorprivacy
import android.app.AppOpsManager
+import android.app.KeyguardManager
+import android.app.KeyguardManager.KeyguardDismissCallback
import android.content.DialogInterface
import android.content.Intent.EXTRA_PACKAGE_NAME
import android.content.pm.PackageManager
import android.content.res.Resources
import android.hardware.SensorPrivacyManager
-import android.hardware.SensorPrivacyManager.*
+import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
+import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA
+import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE
import android.os.Bundle
import android.text.Html
+import android.util.Log
import com.android.internal.app.AlertActivity
import com.android.systemui.R
@@ -35,18 +40,29 @@
* <p>The dialog is started for the user the app is running for which might be a secondary users.
*/
class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListener {
+
+ companion object {
+ private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
+ }
+
private var sensor = -1
private lateinit var sensorUsePackageName: String
private lateinit var sensorPrivacyManager: SensorPrivacyManager
private lateinit var appOpsManager: AppOpsManager
+ private lateinit var keyguardManager: KeyguardManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ setShowWhenLocked(true)
+
+ setFinishOnTouchOutside(false)
+
setResult(RESULT_CANCELED)
sensorPrivacyManager = getSystemService(SensorPrivacyManager::class.java)!!
appOpsManager = getSystemService(AppOpsManager::class.java)!!
+ keyguardManager = getSystemService(KeyguardManager::class.java)!!
sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return
sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also {
@@ -107,8 +123,23 @@
override fun onClick(dialog: DialogInterface?, which: Int) {
when (which) {
BUTTON_POSITIVE -> {
- sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
- setResult(RESULT_OK)
+ if (keyguardManager.isDeviceLocked) {
+ keyguardManager
+ .requestDismissKeyguard(this, object : KeyguardDismissCallback() {
+ override fun onDismissError() {
+ Log.e(LOG_TAG, "Cannot dismiss keyguard")
+ }
+
+ override fun onDismissSucceeded() {
+ sensorPrivacyManager
+ .setIndividualSensorPrivacyForProfileGroup(sensor, false)
+ setResult(RESULT_OK)
+ }
+ })
+ } else {
+ sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
+ setResult(RESULT_OK)
+ }
}
}
@@ -120,4 +151,8 @@
sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
}
+
+ override fun onBackPressed() {
+ // do not allow backing out
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
index 8dcc8b4..3c7d78c 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
@@ -30,25 +29,21 @@
private static final String THICK_BRIGHTNESS_SLIDER = "sysui_thick_brightness";
private final FeatureFlags mFeatureFlags;
- private final boolean mUseThickSlider;
- private final boolean mUseMirrorOnThickSlider;
@Inject
- public BrightnessControllerSettings(SecureSettings settings, FeatureFlags featureFlags) {
+ public BrightnessControllerSettings(FeatureFlags featureFlags) {
mFeatureFlags = featureFlags;
- mUseThickSlider = settings.getInt(THICK_BRIGHTNESS_SLIDER, 0) != 0;
- mUseMirrorOnThickSlider = settings.getInt(THICK_BRIGHTNESS_SLIDER, 0) != 2;
}
// Changing this setting between zero and non-zero may crash systemui down the line. Better to
// restart systemui after changing it.
/** */
boolean useThickSlider() {
- return mUseThickSlider && mFeatureFlags.useNewBrightnessSlider();
+ return mFeatureFlags.useNewBrightnessSlider();
}
/** */
boolean useMirrorOnThickSlider() {
- return !useThickSlider() || (useThickSlider() && mUseMirrorOnThickSlider);
+ return !useThickSlider();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
index 53ff1df..a6aec3b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
@@ -17,7 +17,6 @@
package com.android.systemui.settings.brightness;
import android.content.Context;
-import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.view.LayoutInflater;
@@ -33,6 +32,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.RoundedCornerProgressDrawable;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -292,8 +292,8 @@
if (b.getProgressDrawable() instanceof LayerDrawable) {
Drawable progress = ((LayerDrawable) b.getProgressDrawable())
.findDrawableByLayerId(com.android.internal.R.id.progress);
- if (progress instanceof ClipDrawable) {
- Drawable inner = ((ClipDrawable) progress).getDrawable();
+ if (progress instanceof RoundedCornerProgressDrawable) {
+ Drawable inner = ((RoundedCornerProgressDrawable) progress).getDrawable();
if (inner instanceof LayerDrawable) {
return (LayerDrawable) inner;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 1ec043c..e4ae560 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -23,8 +23,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
@@ -41,7 +39,6 @@
private final Context mContext;
private final InjectionInflationController mInjectionInflationController;
- private final LockscreenLockIconController mLockIconController;
private final NotificationShelfComponent.Builder mNotificationShelfComponentBuilder;
private NotificationShadeWindowView mNotificationShadeWindowView;
@@ -51,11 +48,9 @@
@Inject
public SuperStatusBarViewFactory(Context context,
InjectionInflationController injectionInflationController,
- NotificationShelfComponent.Builder notificationShelfComponentBuilder,
- LockscreenLockIconController lockIconController) {
+ NotificationShelfComponent.Builder notificationShelfComponentBuilder) {
mContext = context;
mInjectionInflationController = injectionInflationController;
- mLockIconController = lockIconController;
mNotificationShelfComponentBuilder = notificationShelfComponentBuilder;
}
@@ -77,10 +72,6 @@
throw new IllegalStateException(
"R.layout.super_notification_shade could not be properly inflated");
}
- LockIcon lockIcon = mNotificationShadeWindowView.findViewById(R.id.lock_icon);
- if (lockIcon != null) {
- mLockIconController.attach(lockIcon);
- }
return mNotificationShadeWindowView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 93156d8..0957f78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -69,6 +69,8 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
@@ -89,6 +91,10 @@
*/
@Module(includes = { NotificationSectionHeadersModule.class })
public interface NotificationsModule {
+ @Binds
+ StackScrollAlgorithm.SectionProvider bindSectionProvider(
+ NotificationSectionsManager impl);
+
/** Provides an instance of {@link NotificationEntryManager} */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index a12179c..756fe6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -22,6 +22,7 @@
import android.util.MathUtils;
import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -30,9 +31,12 @@
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider;
+import javax.inject.Inject;
+
/**
* A global state to track all input states for the algorithm.
*/
+@SysUISingleton
public class AmbientState {
private static final float MAX_PULSE_HEIGHT = 100000f;
@@ -83,6 +87,7 @@
/** Tracks the state from AlertingNotificationManager#hasNotifications() */
private boolean mHasAlertEntries;
+ @Inject
public AmbientState(
Context context,
@NonNull SectionProvider sectionProvider) {
@@ -98,7 +103,7 @@
mBaseZHeight = getBaseHeight(mZDistanceBetweenElements);
}
- void setIsShadeOpening(boolean isOpening) {
+ public void setIsShadeOpening(boolean isOpening) {
mIsShadeOpening = isOpening;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3f3be44..2c7c5cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -491,7 +491,8 @@
NotificationSectionsManager notificationSectionsManager,
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
- SysuiStatusBarStateController statusbarStateController
+ SysuiStatusBarStateController statusbarStateController,
+ AmbientState ambientState
) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -500,7 +501,7 @@
mSectionsManager.initialize(this, LayoutInflater.from(context));
mSections = mSectionsManager.createSectionsForBuckets();
- mAmbientState = new AmbientState(context, mSectionsManager);
+ mAmbientState = ambientState;
mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
.getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
@@ -549,10 +550,6 @@
}
}
- void setIsShadeOpening(boolean isOpening) {
- mAmbientState.setIsShadeOpening(isOpening);
- }
-
void setSectionPadding(float margin) {
mAmbientState.setSectionPadding(margin);
requestChildrenUpdate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 879dad8..a516742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -45,7 +45,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
@@ -271,10 +270,6 @@
}
};
- public void setIsShadeOpening(boolean isOpening) {
- mView.setIsShadeOpening(isOpening);
- }
-
public void setSectionPadding(float padding) {
mView.setSectionPadding(padding);
}
@@ -851,11 +846,14 @@
return mView.getChildAtRawPosition(x, y);
}
- public FrameLayout.LayoutParams getLayoutParams() {
- return (FrameLayout.LayoutParams) mView.getLayoutParams();
+ public ViewGroup.LayoutParams getLayoutParams() {
+ return mView.getLayoutParams();
}
- public void setLayoutParams(FrameLayout.LayoutParams lp) {
+ /**
+ * Updates layout parameters on the root view
+ */
+ public void setLayoutParams(ViewGroup.LayoutParams lp) {
mView.setLayoutParams(lp);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index e1eaf3c..f289b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -71,7 +71,7 @@
"persist.sysui.wake_performs_auth", true);
private boolean mDozingRequested;
private boolean mPulsing;
- private WakefulnessLifecycle mWakefulnessLifecycle;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private final SysuiStatusBarStateController mStatusBarStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final HeadsUpManagerPhone mHeadsUpManagerPhone;
@@ -86,9 +86,8 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
- private final LockscreenLockIconController mLockscreenLockIconController;
private final AuthController mAuthController;
- private NotificationIconAreaController mNotificationIconAreaController;
+ private final NotificationIconAreaController mNotificationIconAreaController;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private NotificationPanelViewController mNotificationPanel;
private View mAmbientIndicationContainer;
@@ -109,7 +108,6 @@
PulseExpansionHandler pulseExpansionHandler,
NotificationShadeWindowController notificationShadeWindowController,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
- LockscreenLockIconController lockscreenLockIconController,
AuthController authController,
NotificationIconAreaController notificationIconAreaController) {
super();
@@ -129,7 +127,6 @@
mPulseExpansionHandler = pulseExpansionHandler;
mNotificationShadeWindowController = notificationShadeWindowController;
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
- mLockscreenLockIconController = lockscreenLockIconController;
mAuthController = authController;
mNotificationIconAreaController = notificationIconAreaController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index eceac32..7011eee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -37,12 +37,10 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -52,18 +50,20 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ViewController;
import java.util.Optional;
import javax.inject.Inject;
-/** Controls the {@link LockIcon} in the lockscreen. */
-@SysUISingleton
-public class LockscreenLockIconController {
+/** Controls the {@link LockIcon} on the lockscreen. */
+@StatusBarComponent.StatusBarScope
+public class LockscreenLockIconController extends ViewController<LockIcon> {
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -79,7 +79,6 @@
private final KeyguardStateController mKeyguardStateController;
private final Resources mResources;
private final HeadsUpManagerPhone mHeadsUpManagerPhone;
- private final KeyguardSecurityModel mKeyguardSecurityModel;
private boolean mKeyguardShowing;
private boolean mKeyguardJustShown;
private boolean mBlockUpdates;
@@ -92,50 +91,308 @@
private boolean mBouncerShowingScrimmed;
private boolean mFingerprintUnlock;
private int mStatusBarState = StatusBarState.SHADE;
- private LockIcon mLockIcon;
+ private int mLastState;
+ private boolean mDozing;
- private View.OnAttachStateChangeListener mOnAttachStateChangeListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- mStatusBarStateController.addCallback(mSBStateListener);
- mConfigurationController.addCallback(mConfigurationListener);
- mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
- mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
- mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+ @Inject
+ public LockscreenLockIconController(
+ @Nullable LockIcon view,
+ LockscreenGestureLogger lockscreenGestureLogger,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils,
+ ShadeController shadeController,
+ AccessibilityController accessibilityController,
+ KeyguardIndicationController keyguardIndicationController,
+ StatusBarStateController statusBarStateController,
+ ConfigurationController configurationController,
+ NotificationWakeUpCoordinator notificationWakeUpCoordinator,
+ KeyguardBypassController keyguardBypassController,
+ @Nullable DockManager dockManager,
+ KeyguardStateController keyguardStateController,
+ @Main Resources resources,
+ HeadsUpManagerPhone headsUpManagerPhone) {
+ super(view);
+ mLockscreenGestureLogger = lockscreenGestureLogger;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
+ mShadeController = shadeController;
+ mAccessibilityController = accessibilityController;
+ mKeyguardIndicationController = keyguardIndicationController;
+ mStatusBarStateController = statusBarStateController;
+ mConfigurationController = configurationController;
+ mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+ mKeyguardBypassController = keyguardBypassController;
+ mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
+ mKeyguardStateController = keyguardStateController;
+ mResources = resources;
+ mHeadsUpManagerPhone = headsUpManagerPhone;
- mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+ if (view != null) {
+ mKeyguardIndicationController.setLockIconController(this);
+ }
+ }
- mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
- mConfigurationListener.onThemeChanged();
+ @Override
+ protected void onInit() {
+ if (mView == null) {
+ return;
+ }
+ mView.setOnClickListener(this::handleClick);
+ mView.setOnLongClickListener(this::handleLongClick);
+ mView.setAccessibilityDelegate(mAccessibilityDelegate);
+ }
- updateColor();
+ @Override
+ protected void onViewAttached() {
+ setStatusBarState(mStatusBarStateController.getState());
+ mDozing = mStatusBarStateController.isDozing();
+ mStatusBarStateController.addCallback(mSBStateListener);
+ mConfigurationController.addCallback(mConfigurationListener);
+ mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
+ mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+ mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+
+ mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+
+ mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
+ mConfigurationListener.onThemeChanged();
+
+ updateColor();
+ update();
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mStatusBarStateController.removeCallback(mSBStateListener);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
+ mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+ mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
+
+ mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+ }
+
+ /**
+ * Called whenever the scrims become opaque, transparent or semi-transparent.
+ */
+ public void onScrimVisibilityChanged(Integer scrimsVisible) {
+ if (mWakeAndUnlockRunning
+ && scrimsVisible == ScrimController.TRANSPARENT) {
+ mWakeAndUnlockRunning = false;
update();
}
+ }
- @Override
- public void onViewDetachedFromWindow(View v) {
- mStatusBarStateController.removeCallback(mSBStateListener);
- mConfigurationController.removeCallback(mConfigurationListener);
- mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
- mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
- mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
-
- mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+ /**
+ * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
+ * icon on top of the black front scrim.
+ * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
+ * keyguard with fingerprint.
+ * @param wakeAndUnlock are we wake and unlocking
+ * @param isUnlock are we currently unlocking
+ */
+ public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
+ BiometricSourceType type) {
+ if (wakeAndUnlock) {
+ mWakeAndUnlockRunning = true;
}
- };
+ mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
+ if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
+ && canBlockUpdates()) {
+ // We don't want the icon to change while we are unlocking
+ mBlockUpdates = true;
+ }
+ update();
+ }
+
+ /**
+ * When we're launching an affordance, like double pressing power to open camera.
+ */
+ public void onShowingLaunchAffordanceChanged(Boolean showing) {
+ mShowingLaunchAffordance = showing;
+ update();
+ }
+
+ /** Sets whether the bouncer is showing. */
+ public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
+ mBouncerShowingScrimmed = scrimmed;
+ update();
+ }
+
+ /**
+ * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
+ * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
+ */
+ public void setBouncerHideAmount(float hideAmount) {
+ mBouncerHiddenAmount = hideAmount;
+ updateColor();
+ }
+
+ private void updateColor() {
+ if (mView == null) {
+ return;
+ }
+ int iconColor = -1;
+ if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
+ TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+ null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+ iconColor = typedArray.getColor(0, Color.WHITE);
+ typedArray.recycle();
+ } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
+ iconColor = Utils.getColorAttrDefaultColor(
+ mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+ } else {
+ // bouncer is transitioning
+ TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+ null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+ int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
+ typedArray.recycle();
+ int keyguardIconColor = Utils.getColorAttrDefaultColor(
+ mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+ iconColor = (int) new ArgbEvaluator().evaluate(
+ mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
+ }
+ mView.updateColor(iconColor);
+ }
+
+ /**
+ * Animate padlock opening when bouncer challenge is solved.
+ */
+ public void onBouncerPreHideAnimation() {
+ update();
+ }
+
+ /**
+ * If we're currently presenting an authentication error message.
+ */
+ public void setTransientBiometricsError(boolean transientBiometricsError) {
+ mTransientBiometricsError = transientBiometricsError;
+ update();
+ }
+
+ private boolean handleLongClick(View view) {
+ mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+ mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
+ mKeyguardIndicationController.showTransientIndication(
+ R.string.keyguard_indication_trust_disabled);
+ mKeyguardUpdateMonitor.onLockIconPressed();
+ mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+
+ return true;
+ }
+
+
+ private void handleClick(View view) {
+ if (!mAccessibilityController.isAccessibilityEnabled()) {
+ return;
+ }
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ }
+
+ private void update() {
+ update(false /* force */);
+ }
+
+ private void update(boolean force) {
+ if (mView == null) {
+ return;
+ }
+ int state = getState();
+ boolean shouldUpdate = mLastState != state || force;
+ if (mBlockUpdates && canBlockUpdates()) {
+ shouldUpdate = false;
+ }
+ if (shouldUpdate && mView.getVisibility() != GONE) {
+ mView.update(state, mDozing, mKeyguardJustShown);
+ }
+ mLastState = state;
+ mKeyguardJustShown = false;
+ updateIconVisibility();
+ updateClickability();
+ }
+
+ private int getState() {
+ if ((mKeyguardStateController.canDismissLockScreen()
+ || !mKeyguardStateController.isShowing()
+ || mKeyguardStateController.isKeyguardGoingAway()
+ || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
+ return STATE_LOCK_OPEN;
+ } else if (mTransientBiometricsError) {
+ return STATE_BIOMETRICS_ERROR;
+ } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
+ return STATE_SCANNING_FACE;
+ } else {
+ return STATE_LOCKED;
+ }
+ }
+
+ private boolean canBlockUpdates() {
+ return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
+ }
+
+ /** Set the StatusBarState. */
+ private void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ updateIconVisibility();
+ }
+
+ /**
+ * Update the icon visibility
+ * @return true if the visibility changed
+ */
+ private boolean updateIconVisibility() {
+ if (mView == null) {
+ return false;
+ }
+ if (!mKeyguardUpdateMonitor.canShowLockIcon()) {
+ boolean changed = mView.getVisibility() != GONE;
+ mView.setVisibility(GONE);
+ return changed;
+ }
+
+ boolean onAodOrDocked = mDozing || mDocked;
+ boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
+ boolean fingerprintOrBypass = mFingerprintUnlock
+ || mKeyguardBypassController.getBypassEnabled();
+ if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
+ if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
+ || mHeadsUpManagerPhone.hasPinnedHeadsUp()
+ || mStatusBarState == StatusBarState.KEYGUARD
+ || mStatusBarState == StatusBarState.SHADE)
+ && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
+ invisible = true;
+ }
+ }
+ return mView.updateIconVisibility(!invisible);
+ }
+
+ private void updateClickability() {
+ if (mView == null) {
+ return;
+ }
+ boolean canLock = mKeyguardStateController.isMethodSecure()
+ && mKeyguardStateController.canDismissLockScreen();
+ boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
+ mView.setClickable(clickToUnlock);
+ mView.setLongClickable(canLock && !clickToUnlock);
+ mView.setFocusable(mAccessibilityController.isAccessibilityEnabled());
+ }
private final StatusBarStateController.StateListener mSBStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozingChanged(boolean isDozing) {
- setDozing(isDozing);
+ if (mDozing != isDozing) {
+ mDozing = isDozing;
+ update();
+ }
}
@Override
public void onDozeAmountChanged(float linear, float eased) {
- if (mLockIcon != null) {
- mLockIcon.setDozeAmount(eased);
+ if (mView != null) {
+ mView.setDozeAmount(eased);
}
}
@@ -160,29 +417,21 @@
@Override
public void onDensityOrFontScaleChanged() {
- if (mLockIcon == null) {
- return;
- }
-
- ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams();
+ ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp == null) {
return;
}
- lp.width = mLockIcon.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
- lp.height = mLockIcon.getResources().getDimensionPixelSize(
+ lp.width = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
+ lp.height = mView.getResources().getDimensionPixelSize(
R.dimen.keyguard_lock_height);
- mLockIcon.setLayoutParams(lp);
+ mView.setLayoutParams(lp);
update(true /* force */);
}
@Override
public void onLocaleListChanged() {
- if (mLockIcon == null) {
- return;
- }
-
- mLockIcon.setContentDescription(
- mLockIcon.getResources().getText(R.string.accessibility_unlock_button));
+ mView.setContentDescription(
+ mView.getResources().getText(R.string.accessibility_unlock_button));
update(true /* force */);
}
@@ -299,9 +548,9 @@
if (fingerprintRunning && unlockingAllowed) {
AccessibilityNodeInfo.AccessibilityAction unlock =
new AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLICK,
- mResources.getString(
- R.string.accessibility_unlock_without_fingerprint));
+ AccessibilityNodeInfo.ACTION_CLICK,
+ mResources.getString(
+ R.string.accessibility_unlock_without_fingerprint));
info.addAction(unlock);
info.setHintText(mResources.getString(
R.string.accessibility_waiting_for_fingerprint));
@@ -313,277 +562,4 @@
}
}
};
- private int mLastState;
-
- @Inject
- public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- LockPatternUtils lockPatternUtils,
- ShadeController shadeController,
- AccessibilityController accessibilityController,
- KeyguardIndicationController keyguardIndicationController,
- StatusBarStateController statusBarStateController,
- ConfigurationController configurationController,
- NotificationWakeUpCoordinator notificationWakeUpCoordinator,
- KeyguardBypassController keyguardBypassController,
- @Nullable DockManager dockManager,
- KeyguardStateController keyguardStateController,
- @Main Resources resources,
- HeadsUpManagerPhone headsUpManagerPhone,
- KeyguardSecurityModel keyguardSecurityModel) {
- mLockscreenGestureLogger = lockscreenGestureLogger;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLockPatternUtils = lockPatternUtils;
- mShadeController = shadeController;
- mAccessibilityController = accessibilityController;
- mKeyguardIndicationController = keyguardIndicationController;
- mStatusBarStateController = statusBarStateController;
- mConfigurationController = configurationController;
- mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
- mKeyguardBypassController = keyguardBypassController;
- mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
- mKeyguardStateController = keyguardStateController;
- mResources = resources;
- mHeadsUpManagerPhone = headsUpManagerPhone;
- mKeyguardSecurityModel = keyguardSecurityModel;
-
- mKeyguardIndicationController.setLockIconController(this);
- }
-
- /**
- * Associate the controller with a {@link LockIcon}
- *
- * TODO: change to an init method and inject the view.
- */
- public void attach(LockIcon lockIcon) {
- mLockIcon = lockIcon;
-
- mLockIcon.setOnClickListener(this::handleClick);
- mLockIcon.setOnLongClickListener(this::handleLongClick);
- mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
-
- if (mLockIcon.isAttachedToWindow()) {
- mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
- }
- mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- setStatusBarState(mStatusBarStateController.getState());
- }
-
- public LockIcon getView() {
- return mLockIcon;
- }
-
- /**
- * Called whenever the scrims become opaque, transparent or semi-transparent.
- */
- public void onScrimVisibilityChanged(Integer scrimsVisible) {
- if (mWakeAndUnlockRunning
- && scrimsVisible == ScrimController.TRANSPARENT) {
- mWakeAndUnlockRunning = false;
- update();
- }
- }
-
- /**
- * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
- * icon on top of the black front scrim.
- * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
- * keyguard with fingerprint.
- * @param wakeAndUnlock are we wake and unlocking
- * @param isUnlock are we currently unlocking
- */
- public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
- BiometricSourceType type) {
- if (wakeAndUnlock) {
- mWakeAndUnlockRunning = true;
- }
- mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
- if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
- && canBlockUpdates()) {
- // We don't want the icon to change while we are unlocking
- mBlockUpdates = true;
- }
- update();
- }
-
- /**
- * When we're launching an affordance, like double pressing power to open camera.
- */
- public void onShowingLaunchAffordanceChanged(Boolean showing) {
- mShowingLaunchAffordance = showing;
- update();
- }
-
- /** Sets whether the bouncer is showing. */
- public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
- mBouncerShowingScrimmed = scrimmed;
- update();
- }
-
- /**
- * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
- * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
- */
- public void setBouncerHideAmount(float hideAmount) {
- mBouncerHiddenAmount = hideAmount;
- updateColor();
- }
-
- private void updateColor() {
- if (mLockIcon == null) {
- return;
- }
-
- int iconColor = -1;
- if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
- TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
- null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
- iconColor = typedArray.getColor(0, Color.WHITE);
- typedArray.recycle();
- } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
- iconColor = Utils.getColorAttrDefaultColor(
- mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
- } else {
- // bouncer is transitioning
- TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
- null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
- int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
- typedArray.recycle();
- int keyguardIconColor = Utils.getColorAttrDefaultColor(
- mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
- iconColor = (int) new ArgbEvaluator().evaluate(
- mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
- }
- mLockIcon.updateColor(iconColor);
- }
-
- /**
- * Animate padlock opening when bouncer challenge is solved.
- */
- public void onBouncerPreHideAnimation() {
- update();
- }
-
- /**
- * If we're currently presenting an authentication error message.
- */
- public void setTransientBiometricsError(boolean transientBiometricsError) {
- mTransientBiometricsError = transientBiometricsError;
- update();
- }
-
- private boolean handleLongClick(View view) {
- mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
- 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
- mKeyguardIndicationController.showTransientIndication(
- R.string.keyguard_indication_trust_disabled);
- mKeyguardUpdateMonitor.onLockIconPressed();
- mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
-
- return true;
- }
-
-
- private void handleClick(View view) {
- if (!mAccessibilityController.isAccessibilityEnabled()) {
- return;
- }
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
- }
-
- private void update() {
- update(false /* force */);
- }
-
- private void update(boolean force) {
- int state = getState();
- boolean shouldUpdate = mLastState != state || force;
- if (mBlockUpdates && canBlockUpdates()) {
- shouldUpdate = false;
- }
- if (shouldUpdate && mLockIcon != null && mLockIcon.getVisibility() != GONE) {
- mLockIcon.update(state,
- mStatusBarStateController.isDozing(), mKeyguardJustShown);
- }
- mLastState = state;
- mKeyguardJustShown = false;
- updateIconVisibility();
- updateClickability();
- }
-
- private int getState() {
- if ((mKeyguardStateController.canDismissLockScreen()
- || !mKeyguardStateController.isShowing()
- || mKeyguardStateController.isKeyguardGoingAway()
- || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
- return STATE_LOCK_OPEN;
- } else if (mTransientBiometricsError) {
- return STATE_BIOMETRICS_ERROR;
- } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
- return STATE_SCANNING_FACE;
- } else {
- return STATE_LOCKED;
- }
- }
-
- private boolean canBlockUpdates() {
- return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
- }
-
- private void setDozing(boolean isDozing) {
- update();
- }
-
- /** Set the StatusBarState. */
- private void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
- updateIconVisibility();
- }
-
- /**
- * Update the icon visibility
- * @return true if the visibility changed
- */
- private boolean updateIconVisibility() {
- if (mLockIcon == null) {
- return false;
- }
-
- if (!mKeyguardUpdateMonitor.shouldShowLockIcon()) {
- boolean changed = mLockIcon.getVisibility() != GONE;
- mLockIcon.setVisibility(GONE);
- return changed;
- }
-
- boolean onAodOrDocked = mStatusBarStateController.isDozing() || mDocked;
- boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
- boolean fingerprintOrBypass = mFingerprintUnlock
- || mKeyguardBypassController.getBypassEnabled();
- if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
- if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
- || mHeadsUpManagerPhone.hasPinnedHeadsUp()
- || mStatusBarState == StatusBarState.KEYGUARD
- || mStatusBarState == StatusBarState.SHADE)
- && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
- invisible = true;
- }
- }
- return mLockIcon.updateIconVisibility(!invisible);
- }
-
- private void updateClickability() {
- if (mAccessibilityController == null) {
- return;
- }
- boolean canLock = mKeyguardStateController.isMethodSecure()
- && mKeyguardStateController.canDismissLockScreen();
- boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
- if (mLockIcon != null) {
- mLockIcon.setClickable(clickToUnlock);
- mLockIcon.setLongClickable(canLock && !clickToUnlock);
- mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
- }
- }
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index b24d0e7..a30f193 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -120,6 +120,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -552,10 +553,12 @@
AuthController authController,
QSDetailDisplayer qsDetailDisplayer,
ScrimController scrimController,
- MediaDataManager mediaDataManager) {
+ MediaDataManager mediaDataManager,
+ AmbientState ambientState) {
super(view, falsingManager, dozeLog, keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
- latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager);
+ latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager,
+ ambientState);
mView = view;
mMetricsLogger = metricsLogger;
mActivityManager = activityManager;
@@ -753,19 +756,16 @@
public void updateResources() {
int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
- int panelGravity = mResources.getInteger(R.integer.notification_panel_layout_gravity);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
- if (lp.width != qsWidth || lp.gravity != panelGravity) {
+ ViewGroup.LayoutParams lp = mQsFrame.getLayoutParams();
+ if (lp.width != qsWidth) {
lp.width = qsWidth;
- lp.gravity = panelGravity;
mQsFrame.setLayoutParams(lp);
}
int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
lp = mNotificationStackScrollLayoutController.getLayoutParams();
- if (lp.width != panelWidth || lp.gravity != panelGravity) {
+ if (lp.width != panelWidth) {
lp.width = panelWidth;
- lp.gravity = panelGravity;
mNotificationStackScrollLayoutController.setLayoutParams(lp);
}
}
@@ -910,7 +910,7 @@
clockPreferredY, hasCustomClock(),
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
- mUpdateMonitor.shouldShowLockIcon(),
+ mUpdateMonitor.canShowLockIcon(),
getQsExpansionFraction(),
mDisplayCutoutTopInset);
mClockPositionAlgorithm.run(mClockPositionResult);
@@ -2411,11 +2411,6 @@
}
@Override
- protected void setIsShadeOpening(boolean isOpening) {
- mNotificationStackScrollLayoutController.setIsShadeOpening(isOpening);
- }
-
- @Override
public void setSectionPadding(float padding) {
if (padding == mSectionPadding) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 27c94d2..b367406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -28,6 +28,7 @@
import android.widget.FrameLayout;
import androidx.annotation.DimenRes;
+import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.systemui.R;
import com.android.systemui.fragments.FragmentHostManager;
@@ -42,7 +43,7 @@
/**
* The container with notification stack scroller and quick settings inside.
*/
-public class NotificationsQuickSettingsContainer extends FrameLayout
+public class NotificationsQuickSettingsContainer extends ConstraintLayout
implements OnInflateListener, FragmentListener,
AboveShelfObserver.HasViewAboveShelfChangedListener {
@@ -68,11 +69,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mQsFrame = (FrameLayout) findViewById(R.id.qs_frame);
+ mQsFrame = findViewById(R.id.qs_frame);
mStackScroller = findViewById(R.id.notification_stack_scroller);
mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
- ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher);
+ ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher);
userSwitcher.setOnInflateListener(this);
mUserSwitcher = userSwitcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index bee0cb1..28cfe21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -53,7 +53,7 @@
if (DEBUG) LOG("go state: %d -> %d", mState, state);
mState = state;
if (mPanel != null) {
- mPanel.setIsShadeOpening(state == STATE_OPENING);
+ mPanel.getAmbientState().setIsShadeOpening(state == STATE_OPENING);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 3031b8a..55744f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -27,6 +27,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.SystemClock;
@@ -54,6 +55,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -145,6 +147,9 @@
private float mInitialTouchX;
private boolean mTouchDisabled;
+ // AmbientState will never be null since it provides an @Inject constructor for Dagger to call.
+ private AmbientState mAmbientState;
+
/**
* Whether or not the PanelView can be expanded or collapsed with a drag.
*/
@@ -223,13 +228,19 @@
mJustPeeked = true;
}
+ protected AmbientState getAmbientState() {
+ return mAmbientState;
+ }
+
public PanelViewController(PanelView view,
FalsingManager falsingManager, DozeLog dozeLog,
KeyguardStateController keyguardStateController,
SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper,
LatencyTracker latencyTracker,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
- StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+ StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ AmbientState ambientState) {
+ mAmbientState = ambientState;
mView = view;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -776,8 +787,6 @@
*/
protected abstract boolean isTrackingBlocked();
- protected abstract void setIsShadeOpening(boolean isShadeOpening);
-
protected abstract void setSectionPadding(float padding);
protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c0a5ffa..31a432e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.leak.RotationUtils;
+import java.util.List;
import java.util.Objects;
public class PhoneStatusBarView extends PanelBar {
@@ -75,6 +76,8 @@
@Nullable
private DisplayCutout mDisplayCutout;
private int mStatusBarHeight;
+ @Nullable
+ private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -93,6 +96,11 @@
mBar = bar;
}
+ public void setExpansionChangedListeners(
+ @Nullable List<StatusBar.ExpansionChangedListener> listeners) {
+ mExpansionChangedListeners = listeners;
+ }
+
public void setScrimController(ScrimController scrimController) {
mScrimController = scrimController;
}
@@ -277,6 +285,12 @@
if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) {
mBar.getNavigationBarView().onStatusBarPanelStateChanged();
}
+
+ if (mExpansionChangedListeners != null) {
+ for (StatusBar.ExpansionChangedListener listener : mExpansionChangedListeners) {
+ listener.onExpansionChanged(frac, expanded);
+ }
+ }
}
private void updateScrimFraction() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e39065b..7b2330b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,7 +34,6 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -64,8 +63,6 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -201,16 +198,6 @@
private boolean mWakeLockHeld;
private boolean mKeyguardOccluded;
- /**
- * Notifies listeners of animation-related changes (currently just opacity changes).
- */
- public interface ScrimChangedListener {
- void onAlphaChanged(float alpha);
- }
-
- @NonNull
- private final List<ScrimChangedListener> mScrimChangedListeners;
-
@Inject
public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters,
AlarmManager alarmManager, KeyguardStateController keyguardStateController,
@@ -223,7 +210,6 @@
ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(featureFlags.isShadeOpaque()
? BUSY_SCRIM_ALPHA : GAR_SCRIM_ALPHA);
mBlurUtils = blurUtils;
- mScrimChangedListeners = new ArrayList<>();
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -301,10 +287,6 @@
mScrimVisibleListener = listener;
}
- public void addScrimChangedListener(@NonNull ScrimChangedListener listener) {
- mScrimChangedListeners.add(listener);
- }
-
public void transitionTo(ScrimState state) {
transitionTo(state, null);
}
@@ -580,10 +562,6 @@
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha);
}
-
- for (ScrimChangedListener listener : mScrimChangedListeners) {
- listener.onAlphaChanged(mBehindAlpha);
- }
}
private void applyAndDispatchExpansion() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 8365139..7095afd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -338,6 +338,10 @@
ONLY_CORE_APPS = onlyCoreApps;
}
+ public interface ExpansionChangedListener {
+ void onExpansionChanged(float expansion, boolean expanded);
+ }
+
/**
* The {@link StatusBarState} of the status bar.
*/
@@ -368,7 +372,6 @@
protected NotificationShadeWindowController mNotificationShadeWindowController;
protected StatusBarWindowController mStatusBarWindowController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final LockscreenLockIconController mLockscreenLockIconController;
@VisibleForTesting
DozeServiceHost mDozeServiceHost;
private boolean mWakeUpComingFromTouch;
@@ -415,6 +418,7 @@
// expanded notifications
// the sliding/resizing panel within the notification window
protected NotificationPanelViewController mNotificationPanelViewController;
+ protected LockscreenLockIconController mLockscreenLockIconController;
// settings
private QSPanelController mQSPanelController;
@@ -439,6 +443,8 @@
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSlider.Factory mBrightnessSliderFactory;
+ private final List<ExpansionChangedListener> mExpansionChangedListeners;
+
// for disabling the status bar
private int mDisabled1 = 0;
private int mDisabled2 = 0;
@@ -725,7 +731,6 @@
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
- LockscreenLockIconController lockscreenLockIconController,
DozeParameters dozeParameters,
ScrimController scrimController,
@Nullable KeyguardLiftController keyguardLiftController,
@@ -807,7 +812,6 @@
mAssistManagerLazy = assistManagerLazy;
mConfigurationController = configurationController;
mNotificationShadeWindowController = notificationShadeWindowController;
- mLockscreenLockIconController = lockscreenLockIconController;
mDozeServiceHost = dozeServiceHost;
mPowerManager = powerManager;
mDozeParameters = dozeParameters;
@@ -840,6 +844,8 @@
mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
+ mExpansionChangedListeners = new ArrayList<>();
+
mBubbleExpandListener =
(isExpanding, key) -> {
mContext.getMainExecutor().execute(() -> {
@@ -1079,6 +1085,7 @@
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setScrimController(mScrimController);
+ mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
// CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
@@ -1173,9 +1180,7 @@
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
- if (mNotificationShadeWindowView != null) {
- mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
- }
+ mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
});
mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
@@ -1208,9 +1213,6 @@
createUserSwitcher();
}
- mNotificationPanelViewController.setLaunchAffordanceListener(
- mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
-
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
@@ -1490,6 +1492,11 @@
mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
+ mLockscreenLockIconController = statusBarComponent.getLockscreenLockIconController();
+ mLockscreenLockIconController.init();
+
+ mNotificationPanelViewController.setLaunchAffordanceListener(
+ mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
}
protected void startKeyguard() {
@@ -4552,4 +4559,8 @@
public void suppressAmbientDisplay(boolean suppressed) {
mDozeServiceHost.setDozeSuppressed(suppressed);
}
+
+ public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
+ mExpansionChangedListeners.add(listener);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 5f90077..f6165f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -316,11 +316,23 @@
mIconController.removeAllIconsForSlot(mSlotMobile);
mMobileStates.clear();
+ List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
+ noCallingStates.addAll(mNoCallingStates);
mNoCallingStates.clear();
final int n = subs.size();
for (int i = 0; i < n; i++) {
mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
- mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+ boolean isNewSub = true;
+ for (NoCallingIconState state : noCallingStates) {
+ if (state.subId == subs.get(i).getSubscriptionId()) {
+ mNoCallingStates.add(state);
+ isNewSub = false;
+ break;
+ }
+ }
+ if (isNewSub) {
+ mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 802da3e..ecd9613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -18,6 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
@@ -73,4 +74,9 @@
@StatusBarScope
NotificationPanelViewController getNotificationPanelViewController();
+ /**
+ * Creates a LockscreenLockIconController.
+ */
+ @StatusBarScope
+ LockscreenLockIconController getLockscreenLockIconController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 26e1959..9e9533d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -77,7 +77,6 @@
import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightsOutNotifController;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
@@ -167,7 +166,6 @@
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
- LockscreenLockIconController lockscreenLockIconController,
DozeParameters dozeParameters,
ScrimController scrimController,
@Nullable KeyguardLiftController keyguardLiftController,
@@ -248,7 +246,6 @@
assistManagerLazy,
configurationController,
notificationShadeWindowController,
- lockscreenLockIconController,
dozeParameters,
scrimController,
keyguardLiftController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 37d8c9a..781abe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar.phone.dagger;
+import android.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
@@ -32,4 +36,12 @@
return notificationShadeWindowView.getNotificationPanelView();
}
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ @Nullable
+ public static LockIcon getLockIcon(
+ NotificationShadeWindowView notificationShadeWindowView) {
+ return notificationShadeWindowView.findViewById(R.id.lock_icon);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
index 2eff04e..80b75a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
@@ -53,13 +53,21 @@
public void notifyListeners(SignalCallback callback) {
boolean ethernetVisible = mCurrentState.connected;
String contentDescription = getTextIfExists(getContentDescription()).toString();
-
// TODO: wire up data transfer using WifiSignalPoller.
callback.setEthernetIndicators(new IconState(ethernetVisible, getCurrentIconId(),
contentDescription));
}
@Override
+ public int getContentDescription() {
+ if (mCurrentState.connected) {
+ return getIcons().contentDesc[1];
+ } else {
+ return getIcons().discContentDesc;
+ }
+ }
+
+ @Override
public State cleanState() {
return new State();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f9450ae..e13e30b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -524,6 +524,10 @@
return mWifiSignalController.isCarrierMergedWifi(subId);
}
+ boolean isEthernetDefault() {
+ return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
+ }
+
String getNonDefaultMobileDataNetworkName(int subId) {
MobileSignalController controller = getControllerWithSubId(subId);
return controller != null ? controller.getNonDefaultCarrierName() : "";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 9669522..b2120d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -106,7 +106,8 @@
IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
if (mProviderModel) {
IconState qsIcon = null;
- if (mCurrentState.isDefault) {
+ if (mCurrentState.isDefault || (!mNetworkController.isRadioOn()
+ && !mNetworkController.isEthernetDefault())) {
qsIcon = new IconState(mCurrentState.connected,
mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected
: getQsCurrentIconId(), contentDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
new file mode 100644
index 0000000..1af2c9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.graphics.Canvas
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+import android.util.AttributeSet
+import com.android.systemui.R
+import org.xmlpull.v1.XmlPullParser
+
+/**
+ * [DrawableWrapper] to use in the progress of a slider.
+ *
+ * This drawable is used to change the bounds of the enclosed drawable depending on the level to
+ * simulate a sliding progress, instead of using clipping or scaling. That way, the shape of the
+ * edges is maintained.
+ *
+ * Meant to be used with a rounded ends background, it will also prevent deformation when the slider
+ * is meant to be smaller than the rounded corner. The background should have rounded corners that
+ * are half of the height.
+ */
+class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawable) {
+
+ constructor() : this(null)
+
+ companion object {
+ private const val MAX_LEVEL = 10000 // Taken from Drawable
+ }
+
+ private var clipPath: Path = Path()
+
+ init {
+ setClipPath(Rect())
+ }
+
+ override fun inflate(
+ r: Resources,
+ parser: XmlPullParser,
+ attrs: AttributeSet,
+ theme: Resources.Theme?
+ ) {
+ val a = obtainAttributes(r, theme, attrs, R.styleable.RoundedCornerProgressDrawable)
+
+ // Inflation will advance the XmlPullParser and AttributeSet.
+ super.inflate(r, parser, attrs, theme)
+
+ updateStateFromTypedArray(a)
+ if (drawable == null) {
+ throw IllegalStateException("${this::class.java.simpleName} needs a drawable")
+ }
+ a.recycle()
+ }
+
+ override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+ onLevelChange(level)
+ return super.onLayoutDirectionChanged(layoutDirection)
+ }
+
+ private fun updateStateFromTypedArray(a: TypedArray) {
+ if (a.hasValue(R.styleable.RoundedCornerProgressDrawable_android_drawable)) {
+ setDrawable(a.getDrawable(R.styleable.RoundedCornerProgressDrawable_android_drawable))
+ }
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ setClipPath(bounds)
+ super.onBoundsChange(bounds)
+ onLevelChange(level)
+ }
+
+ private fun setClipPath(bounds: Rect) {
+ clipPath.reset()
+ clipPath.addRoundRect(
+ bounds.left.toFloat(),
+ bounds.top.toFloat(),
+ bounds.right.toFloat(),
+ bounds.bottom.toFloat(),
+ bounds.height().toFloat() / 2,
+ bounds.height().toFloat() / 2,
+ Path.Direction.CW
+ )
+ }
+
+ override fun onLevelChange(level: Int): Boolean {
+ val db = drawable?.bounds!!
+ val width = bounds.width() * level / MAX_LEVEL
+ // Extra space on the left to keep the rounded shape on the right end
+ val leftBound = bounds.left - bounds.height()
+ drawable?.setBounds(leftBound, db.top, bounds.left + width, db.bottom)
+ return super.onLevelChange(level)
+ }
+
+ override fun draw(canvas: Canvas) {
+ canvas.save()
+ canvas.clipPath(clipPath)
+ super.draw(canvas)
+ canvas.restore()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 416de04..0795d89 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -29,15 +29,19 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
import com.android.wm.shell.pip.tv.TvPipNotificationController;
+import com.android.wm.shell.pip.tv.TvPipTransition;
+import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -58,6 +62,7 @@
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
+ PipTransitionController pipTransitionController,
TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -68,6 +73,7 @@
pipBoundsState,
pipBoundsAlgorithm,
pipTaskOrganizer,
+ pipTransitionController,
tvPipMenuController,
pipMediaController,
tvPipNotificationController,
@@ -92,6 +98,16 @@
// Handler needed for loadDrawableAsync() in PipControlsViewController
@WMSingleton
@Provides
+ static PipTransitionController provideTvPipTransition(
+ Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
+ PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipBoundsState pipBoundsState, TvPipMenuController pipMenuController) {
+ return new TvPipTransition(pipBoundsState, pipMenuController,
+ pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
+ }
+
+ @WMSingleton
+ @Provides
static TvPipMenuController providesTvPipMenuController(
Context context,
PipBoundsState pipBoundsState,
@@ -113,16 +129,26 @@
@WMSingleton
@Provides
+ static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+ pipSurfaceTransactionHelper) {
+ return new PipAnimationController(pipSurfaceTransactionHelper);
+ }
+
+ @WMSingleton
+ @Provides
static PipTaskOrganizer providePipTaskOrganizer(Context context,
TvPipMenuController tvPipMenuController,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipAnimationController pipAnimationController,
+ PipTransitionController pipTransitionController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
- tvPipMenuController, pipSurfaceTransactionHelper, splitScreenOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
+ pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index c2e4e14..b7aa907 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -71,6 +71,8 @@
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
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;
import com.android.wm.shell.transition.Transitions;
@@ -293,8 +295,8 @@
@WMSingleton
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
- Context context) {
- return new ShellTaskOrganizer(mainExecutor, context);
+ Context context, SizeCompatUI sizeCompatUI) {
+ return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
}
@WMSingleton
@@ -380,8 +382,7 @@
@WMSingleton
@Provides
- static FullscreenTaskListener provideFullscreenTaskListener(
- SyncTransactionQueue syncQueue) {
+ static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
return new FullscreenTaskListener(syncQueue);
}
@@ -392,4 +393,12 @@
@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,
+ mainExecutor);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 12a3b5d..2aaa095 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -17,13 +17,11 @@
package com.android.systemui.wmshell;
import android.animation.AnimationHandler;
-import android.app.ActivityTaskManager;
import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
import com.android.systemui.dagger.WMSingleton;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.apppairs.AppPairs;
@@ -41,18 +39,19 @@
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;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
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.splitscreen.SplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -104,12 +103,13 @@
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
- PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
+ PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+ WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, pipMediaController,
- phonePipMenuController, pipTaskOrganizer, pipTouchHandler,
+ phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, mainExecutor));
}
@@ -143,12 +143,13 @@
PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTouchHandler(context, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger,
- mainExecutor);
+ pipBoundsState, pipTaskOrganizer, pipTransitionController,
+ floatingContentCoordinator, pipUiEventLogger, mainExecutor);
}
@WMSingleton
@@ -157,12 +158,32 @@
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
+ PipAnimationController pipAnimationController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ PipTransitionController pipTransitionController,
Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
- menuPhoneController, pipSurfaceTransactionHelper, splitScreenOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
+ pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+ pipSurfaceTransactionHelper) {
+ return new PipAnimationController(pipSurfaceTransactionHelper);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipTransitionController providePipTransitionController(Context context,
+ Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
+ PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipBoundsState pipBoundsState, PhonePipMenuController pipMenuController) {
+ return new PipTransition(context, pipBoundsState, pipMenuController,
+ pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
deleted file mode 100644
index 71a0434..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SizeCompatModeActivityController.RestartActivityButton;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.CommandQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * runtest systemui -c com.android.systemui.SizeCompatModeActivityControllerTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatModeActivityControllerTest extends SysuiTestCase {
- private static final int DISPLAY_ID = 0;
-
- private SizeCompatModeActivityController mController;
- private TaskStackChangeListener mTaskStackListener;
- private @Mock TaskStackChangeListeners mMockTaskListeners;
- private @Mock RestartActivityButton mMockButton;
- private @Mock IBinder mMockActivityToken;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- doReturn(true).when(mMockButton).show();
-
- mController = new SizeCompatModeActivityController(mContext, mMockTaskListeners,
- new CommandQueue(mContext)) {
- @Override
- RestartActivityButton createRestartButton(Context context) {
- return mMockButton;
- };
- };
-
- ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
- ArgumentCaptor.forClass(TaskStackChangeListener.class);
- verify(mMockTaskListeners).registerTaskStackListener(listenerCaptor.capture());
- mTaskStackListener = listenerCaptor.getValue();
- }
-
- @Test
- public void testOnSizeCompatModeActivityChanged() {
- // Verifies that the restart button is added with non-null component name.
- mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
- verify(mMockButton).show();
- verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
-
- // Verifies that the restart button is removed with null component name.
- mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, null /* activityToken */);
- verify(mMockButton).remove();
- }
-
- @Test
- public void testChangeButtonVisibilityOnImeShowHide() {
- mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
-
- // Verifies that the restart button is hidden when IME is visible.
- doReturn(View.VISIBLE).when(mMockButton).getVisibility();
- mController.setImeWindowStatus(DISPLAY_ID, null /* token */, InputMethodService.IME_VISIBLE,
- 0 /* backDisposition */, false /* showImeSwitcher */);
- verify(mMockButton).setVisibility(eq(View.GONE));
-
- // Verifies that the restart button is visible when IME is hidden.
- doReturn(View.GONE).when(mMockButton).getVisibility();
- mController.setImeWindowStatus(DISPLAY_ID, null /* token */, 0 /* vis */,
- 0 /* backDisposition */, false /* showImeSwitcher */);
- verify(mMockButton).setVisibility(eq(View.VISIBLE));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 72dd442..3e873d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -44,7 +44,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -88,7 +88,7 @@
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
- private ScrimController mScrimController;
+ private StatusBar mStatusBar;
private FakeExecutor mFgExecutor;
@@ -126,7 +126,7 @@
mWindowManager,
mStatusBarStateController,
mFgExecutor,
- mScrimController);
+ mStatusBar);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
@@ -188,9 +188,8 @@
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
event.recycle();
// THEN illumination begins
- verify(mUdfpsView).startIllumination();
// AND onIlluminatedRunnable that notifies FingerprintManager is set
- verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
+ verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
mOnIlluminatedRunnableCaptor.getValue().run();
verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
eq(0), eq(0f), eq(0f));
@@ -205,9 +204,8 @@
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
// THEN illumination begins
- verify(mUdfpsView).startIllumination();
// AND onIlluminatedRunnable that notifies FingerprintManager is set
- verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
+ verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
mOnIlluminatedRunnableCaptor.getValue().run();
verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
eq(0), eq(3f) /* minor */, eq(2f) /* major */);
@@ -243,6 +241,6 @@
@Test
public void registersViewForCallbacks() throws RemoteException {
verify(mStatusBarStateController).addCallback(mUdfpsView);
- verify(mScrimController).addScrimChangedListener(mUdfpsView);
+ verify(mStatusBar).addExpansionChangedListener(mUdfpsView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index 51cf501..47f4183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.verify;
import android.view.View;
+import android.view.WindowInsetsController;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -87,6 +88,8 @@
@Test
public void testOnRotationProposalShowButtonShowNav() {
// No navigation bar should not call to set visibility state
+ mRotationButtonController.onBehaviorChanged(
+ WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
false /* visible */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 2546813..f86b465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -295,8 +295,9 @@
)
controller.showDialog(context)
exhaustExecutors()
- assertThat(dialogProvider.list).hasSize(1)
+ assertThat(dialogProvider.list).hasSize(2)
assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(1L)
+ assertThat(dialogProvider.list?.get(1)?.lastActiveTimestamp).isEqualTo(0L)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
index 9762fff..eb5dd4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
@@ -72,7 +72,7 @@
)
dialog = PrivacyDialog(context, list, starter)
dialog.show()
- dialog.requireViewById<View>(R.id.link).callOnClick()
+ dialog.requireViewById<View>(R.id.privacy_item).callOnClick()
verify(starter).invoke(PrivacyType.TYPE_MICROPHONE.permGroupName)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index c6f97fa..4381158 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -118,7 +118,7 @@
mQSTileHost, mQSCustomizerController, true, mMediaHost,
mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
- /* labelsFlag */ false, /* sideLabels */ false);
+ /* labelsFlag */ false);
mController.init();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index c490c4c..107160f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -83,7 +83,8 @@
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
+ dumpManager,
+ false
)
controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
index b63274b..451c78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -71,7 +72,8 @@
@Mock private NavigationModeController mMockNavModeController;
@Mock private NotificationShadeWindowController mMockStatusBarWinController;
@Mock private Optional<Pip> mMockPipOptional;
- @Mock private Optional<LegacySplitScreen> mMockSplitScreenOptional;
+ @Mock private Optional<LegacySplitScreen> mMockLegacySplitScreenOptional;
+ @Mock private Optional<SplitScreen> mMockSplitScreenOptional;
@Mock private Optional<Lazy<StatusBar>> mMockStatusBarOptionalLazy;
@Mock private Optional<com.android.wm.shell.onehanded.OneHanded> mMockOneHandedOptional;
@Mock private PackageManager mPackageManager;
@@ -89,8 +91,8 @@
mSpiedOverviewProxyService = spy(new OverviewProxyService(mSpiedContext, mMockCommandQueue,
mMockNavBarControllerLazy, mMockNavModeController, mMockStatusBarWinController,
- mMockSysUiState, mMockPipOptional, mMockSplitScreenOptional,
- mMockStatusBarOptionalLazy, mMockOneHandedOptional,
+ mMockSysUiState, mMockPipOptional, mMockLegacySplitScreenOptional,
+ mMockSplitScreenOptional, mMockStatusBarOptionalLazy, mMockOneHandedOptional,
mMockBroadcastDispatcher, mMockTransitions));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 53ff957..fe2f5f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -146,7 +146,8 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically demoted to Silent by the system. "
- + "Was this correct?", prompt.getText().toString());
+ + "Let the developer know your feedback. Was this correct?",
+ prompt.getText().toString());
}
@Test
@@ -157,7 +158,8 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically ranked higher in your shade. "
- + "Was this correct?", prompt.getText().toString());
+ + "Let the developer know your feedback. Was this correct?",
+ prompt.getText().toString());
}
@Test
@@ -168,7 +170,7 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically promoted to Default by the system. "
- + "Was this correct?",
+ + "Let the developer know your feedback. Was this correct?",
prompt.getText().toString());
}
@@ -180,7 +182,8 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically ranked lower in your shade. "
- + "Was this correct?", prompt.getText().toString());
+ + "Let the developer know your feedback. Was this correct?",
+ prompt.getText().toString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ceb4d84..461f64e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -83,6 +83,7 @@
private NotificationStackScrollLayout mStackScroller; // Normally test this
private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below
+ private AmbientState mAmbientState;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Mock private StatusBar mBar;
@@ -123,6 +124,9 @@
});
when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
+ // Interact with real instance of AmbientState.
+ mAmbientState = new AmbientState(mContext, mNotificationSectionsManager);
+
// The actual class under test. You may need to work with this class directly when
// testing anonymous class members of mStackScroller, like mMenuEventListener,
// which refer to members of NotificationStackScrollLayout. The spy
@@ -134,7 +138,8 @@
mNotificationSectionsManager,
mGroupMembershipManger,
mGroupExpansionManager,
- mStatusBarStateController
+ mStatusBarStateController,
+ mAmbientState
);
mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider,
mNotificationSwipeHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 23c0930..bdde822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -88,7 +88,6 @@
@Mock private NotificationPanelViewController mNotificationPanel;
@Mock private View mAmbientIndicationContainer;
@Mock private BiometricUnlockController mBiometricUnlockController;
- @Mock private LockscreenLockIconController mLockscreenLockIconController;
@Mock private AuthController mAuthController;
@Before
@@ -100,7 +99,7 @@
mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
- mLockscreenLockIconController, mAuthController, mNotificationIconAreaController);
+ mAuthController, mNotificationIconAreaController);
mDozeServiceHost.initialize(
mStatusBar,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
index 95a3505..60af16a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
@@ -18,7 +18,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,12 +29,12 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,6 +44,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,7 +64,7 @@
@Mock
private KeyguardIndicationController mKeyguardIndicationController;
@Mock
- private LockIcon mLockIcon; // TODO: make this not a mock once inject is removed.
+ private LockIcon mLockIcon;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -81,34 +81,36 @@
private Resources mResources;
@Mock
private HeadsUpManagerPhone mHeadsUpManagerPhone;
- @Mock
- private KeyguardSecurityModel mKeyguardSecurityModel;
private LockscreenLockIconController mLockIconController;
+
+ @Captor ArgumentCaptor<OnAttachStateChangeListener> mOnAttachStateChangeCaptor;
+ @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+
private OnAttachStateChangeListener mOnAttachStateChangeListener;
+ private StatusBarStateController.StateListener mStatusBarStateListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(true);
when(mLockIcon.getContext()).thenReturn(mContext);
- mLockIconController = new LockscreenLockIconController(
+ mLockIconController = new LockscreenLockIconController(mLockIcon,
mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils,
mShadeController, mAccessibilityController, mKeyguardIndicationController,
mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator,
mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources,
- mHeadsUpManagerPhone, mKeyguardSecurityModel);
+ mHeadsUpManagerPhone);
- ArgumentCaptor<OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
- ArgumentCaptor.forClass(OnAttachStateChangeListener.class);
+ when(mLockIcon.isAttachedToWindow()).thenReturn(true);
+ mLockIconController.init();
- doNothing().when(mLockIcon)
- .addOnAttachStateChangeListener(
- onAttachStateChangeListenerArgumentCaptor.capture());
- mLockIconController.attach(mLockIcon);
-
- mOnAttachStateChangeListener = onAttachStateChangeListenerArgumentCaptor.getValue();
+ verify(mLockIcon).addOnAttachStateChangeListener(
+ mOnAttachStateChangeCaptor.capture());
+ mOnAttachStateChangeListener = mOnAttachStateChangeCaptor.getValue();
+ verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+ mStatusBarStateListener = mStateListenerCaptor.getValue();
}
@Test
@@ -133,23 +135,17 @@
@Test
public void testVisibility_Dozing() {
- ArgumentCaptor<StatusBarStateController.StateListener> sBStateListenerCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-
- mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
- verify(mStatusBarStateController).addCallback(sBStateListenerCaptor.capture());
-
when(mStatusBarStateController.isDozing()).thenReturn(true);
- sBStateListenerCaptor.getValue().onDozingChanged(true);
+ mStatusBarStateListener.onDozingChanged(true);
verify(mLockIcon).updateIconVisibility(false);
}
@Test
public void testVisibility_doNotShowLockIcon() {
- when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(false);
+ mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
- mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
verify(mLockIcon).setVisibility(View.GONE);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index aa7143e..8d4470b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -80,6 +80,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -202,7 +203,8 @@
private ScrimController mScrimController;
@Mock
private MediaDataManager mMediaDataManager;
-
+ @Mock
+ private AmbientState mAmbientState;
private NotificationPanelViewController mNotificationPanelViewController;
private View.AccessibilityDelegate mAccessibiltyDelegate;
@@ -282,7 +284,8 @@
mAuthController,
new QSDetailDisplayer(),
mScrimController,
- mMediaDataManager);
+ mMediaDataManager,
+ mAmbientState);
mNotificationPanelViewController.initDependencies(
mStatusBar,
mNotificationShelfController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 2c781ba..cae488a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -386,7 +386,6 @@
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
- mLockscreenLockIconController,
mDozeParameters,
mScrimController,
mKeyguardLiftController,
@@ -436,6 +435,7 @@
// initialized automatically.
mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
+ mStatusBar.mLockscreenLockIconController = mLockscreenLockIconController;
mStatusBar.mDozeScrimController = mDozeScrimController;
mStatusBar.mPresenter = mNotificationPresenter;
mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index da1f5d3..f8b6383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -37,6 +37,7 @@
import android.app.Instrumentation;
import android.content.Intent;
import android.net.ConnectivityManager;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
@@ -343,6 +344,8 @@
setConnectivityCommon(networkType, validated, isConnected);
if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
if (isConnected) {
+ mNetworkCallback.onAvailable(mock(Network.class),
+ new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
mNetworkCallback.onCapabilitiesChanged(
mock(Network.class), new NetworkCapabilities(mNetCapabilities));
} else {
@@ -357,6 +360,8 @@
setConnectivityCommon(networkType, validated, isConnected);
if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
if (isConnected) {
+ mNetworkCallback.onAvailable(mock(Network.class),
+ new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
mNetworkCallback.onCapabilitiesChanged(
mock(Network.class), new NetworkCapabilities(mNetCapabilities));
} else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 4078d4d..79641db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -18,7 +18,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
@@ -41,7 +40,6 @@
import com.android.wm.shell.onehanded.OneHandedGestureHandler;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +49,12 @@
import java.util.Optional;
+/**
+ * Tests for {@link WMShell}.
+ *
+ * Build/Install/Run:
+ * atest SystemUITests:WMShellTest
+ */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WMShellTest extends SysuiTestCase {
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index ae024ff..5dd271c 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -260,6 +260,10 @@
// Package: android
NOTE_ADB_WIFI_ACTIVE = 62;
+ // Notify the user a carrier suggestion is available to get IMSI exemption.
+ // Package: android
+ NOTE_CARRIER_SUGGESTION_AVAILABLE = 63;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/Android.bp b/services/Android.bp
index 1970b7d..61591c2 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -28,6 +28,7 @@
":services.profcollect-sources",
":services.restrictions-sources",
":services.searchui-sources",
+ ":services.smartspace-sources",
":services.speech-sources",
":services.startop.iorap-sources",
":services.systemcaptions-sources",
@@ -77,6 +78,7 @@
"services.profcollect",
"services.restrictions",
"services.searchui",
+ "services.smartspace",
"services.speech",
"services.startop",
"services.systemcaptions",
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
index 7824fd9..9c54100 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
@@ -44,7 +44,7 @@
@Override
protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mCompletedTapCount + 1 == mTargetFingerCount) {
+ if (mCompletedTapCount + 1 == mTargetTapCount) {
// Calling super.onUp would complete the multi-tap version of this.
cancelGesture(event, rawEvent, policyFlags);
} else {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 04e08ae..55e3ef2 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -926,7 +926,7 @@
mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
deviceProfile, FgThread.getExecutor(), desc -> {
try {
- result.complete(requireNonNull(desc));
+ result.complete(desc);
} catch (Exception e) {
result.completeExceptionally(e);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 6de227e..e01c4df 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -158,9 +158,9 @@
srcs: [":services.core.unboosted"],
tools: ["lockedregioncodeinjection"],
cmd: "$(location lockedregioncodeinjection) " +
- " --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/wm/WindowManagerGlobalLock;\" " +
- " --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection\" " +
- " --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection\" " +
+ " --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/am/ActivityManagerGlobalLock;,Lcom/android/server/wm/WindowManagerGlobalLock;\" " +
+ " --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/am/ActivityManagerService.boostPriorityForProcLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection\" " +
+ " --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/am/ActivityManagerService.resetPriorityAfterProcLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection\" " +
" -o $(out) " +
" -i $(in)",
out: ["services.core.priorityboosted.jar"],
diff --git a/services/core/java/android/power/PowerStatsInternal.java b/services/core/java/android/power/PowerStatsInternal.java
index f7417da..40107a5 100644
--- a/services/core/java/android/power/PowerStatsInternal.java
+++ b/services/core/java/android/power/PowerStatsInternal.java
@@ -18,8 +18,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.power.stats.Channel;
import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.StateResidencyResult;
import java.util.concurrent.CompletableFuture;
@@ -51,4 +55,45 @@
@NonNull
public abstract CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
int[] energyConsumerIds);
+
+ /**
+ * Returns the power entity info for all available {@link PowerEntity}
+ *
+ * @return List of available {@link PowerEntity}
+ */
+ public abstract PowerEntity[] getPowerEntityInfo();
+
+ /**
+ * Returns a CompletableFuture that will get a {@link StateResidencyResult} array for the
+ * available requested power entities.
+ *
+ * @param powerEntityIds Array of {@link PowerEntity.id} for which state residency is being
+ * requested.
+ *
+ * @return A Future containing a list of {@link StateResidencyResult} objects containing state
+ * residency results for all listed {@link PowerEntity.id}.
+ */
+ @NonNull
+ public abstract CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
+ int[] powerEntityIds);
+
+ /**
+ * Returns the channel info for all available {@link Channel}
+ *
+ * @return List of available {@link Channel}
+ */
+ public abstract Channel[] getEnergyMeterInfo();
+
+ /**
+ * Returns a CompletableFuture that will get a {@link EnergyMeasurement} array for the
+ * available requested channels.
+ *
+ * @param channelIds Array of {@link Channel.id} for accumulated energy is being requested.
+ *
+ * @return A Future containing a list of {@link EnergyMeasurement} objects containing
+ * accumulated energy measurements for all listed {@link Channel.id}.
+ */
+ @NonNull
+ public abstract CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
+ int[] channelIds);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4f0efb4..08390b4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -216,14 +216,13 @@
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.utils.PriorityDump;
-import com.google.android.collect.Lists;
-
import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -992,6 +991,15 @@
}
/**
+ * Gets the UID that owns a socket connection. Needed because opening SOCK_DIAG sockets
+ * requires CAP_NET_ADMIN, which the unit tests do not have.
+ */
+ public int getConnectionOwnerUid(int protocol, InetSocketAddress local,
+ InetSocketAddress remote) {
+ return InetDiagMessage.getConnectionOwnerUid(protocol, local, remote);
+ }
+
+ /**
* @see MultinetworkPolicyTracker
*/
public MultinetworkPolicyTracker makeMultinetworkPolicyTracker(
@@ -1023,11 +1031,13 @@
mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
mMetricsLog = logger;
- mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
mNetworkRanker = new NetworkRanker();
- NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
- mNetworkRequests.put(mDefaultRequest, defaultNRI);
- mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
+ final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport(
+ -1, NetworkRequest.Type.REQUEST);
+ mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder());
+ mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
+ mDefaultNetworkRequests.add(mDefaultRequest);
+ mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
mDefaultMobileDataRequest = createDefaultInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
@@ -1577,7 +1587,7 @@
@Override
public NetworkInfo[] getAllNetworkInfo() {
enforceAccessPermission();
- final ArrayList<NetworkInfo> result = Lists.newArrayList();
+ final ArrayList<NetworkInfo> result = new ArrayList<>();
for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
networkType++) {
NetworkInfo info = getNetworkInfo(networkType);
@@ -1638,7 +1648,7 @@
HashMap<Network, NetworkCapabilities> result = new HashMap<>();
- NetworkAgentInfo nai = getDefaultNetwork();
+ final NetworkAgentInfo nai = getDefaultNetwork();
NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
if (nc != null) {
result.put(
@@ -1851,7 +1861,7 @@
// This contains IMSI details, so make sure the caller is privileged.
NetworkStack.checkNetworkStackPermission(mContext);
- final ArrayList<NetworkState> result = Lists.newArrayList();
+ final ArrayList<NetworkState> result = new ArrayList<>();
for (Network network : getAllNetworks()) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai != null) {
@@ -2025,7 +2035,7 @@
// TODO: Move the Dns Event to NetworkMonitor. NetdEventListenerService only allow one
// callback from each caller type. Need to re-factor NetdEventListenerService to allow
// multiple NetworkMonitor registrants.
- if (nai != null && nai.satisfies(mDefaultRequest)) {
+ if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) {
nai.networkMonitor().notifyDnsResponse(returnCode);
}
}
@@ -3443,18 +3453,23 @@
&& currentNetwork.network.getNetId() == nai.network.getNetId()) {
nri.setSatisfier(null, null);
sendUpdatedScoreToFactories(request, null);
+
+ if (mDefaultRequest == nri) {
+ // TODO : make battery stats aware that since 2013 multiple interfaces may be
+ // active at the same time. For now keep calling this with the default
+ // network, because while incorrect this is the closest to the old (also
+ // incorrect) behavior.
+ mNetworkActivityTracker.updateDataActivityTracking(
+ null /* newNetwork */, nai);
+ notifyLockdownVpn(nai);
+ ensureNetworkTransitionWakelock(nai.toShortString());
+ }
}
}
nai.clearLingerState();
- // TODO: this loop, and the mLegacyTypeTracker.remove just below it, seem redundant given
- // there's a full rematch right after. Currently, deleting it breaks tests that check for
- // the default network disconnecting. Find out why, fix the rematch code, and delete this.
- if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
- mDefaultNetworkNai = null;
- mNetworkActivityTracker.updateDataActivityTracking(null /* newNetwork */, nai);
- notifyLockdownVpn(nai);
- ensureNetworkTransitionWakelock(nai.toShortString());
- }
+ // TODO: mLegacyTypeTracker.remove seems redundant given there's a full rematch right after.
+ // Currently, deleting it breaks tests that check for the default network disconnecting.
+ // Find out why, fix the rematch code, and delete this.
mLegacyTypeTracker.remove(nai, wasDefault);
rematchAllNetworksAndRequests();
mLingerMonitor.noteDisconnect(nai);
@@ -3463,10 +3478,9 @@
// (routing rules, DNS, etc).
// This may be slow as it requires a lot of netd shelling out to ip and
// ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
- // after we've rematched networks with requests which should make a potential
- // fallback network the default or requested a new network from the
- // NetworkProviders, so network traffic isn't interrupted for an unnecessarily
- // long time.
+ // after we've rematched networks with requests (which might change the default
+ // network or service a new request from an app), so network traffic isn't interrupted
+ // for an unnecessarily long time.
destroyNativeNetwork(nai);
mDnsManager.removeNetwork(nai.network);
}
@@ -4255,7 +4269,7 @@
@Override
public NetworkRequest getDefaultRequest() {
- return mDefaultRequest;
+ return mDefaultRequest.mRequests.get(0);
}
private class InternalHandler extends Handler {
@@ -4833,7 +4847,7 @@
}
synchronized (mVpns) {
throwIfLockdownEnabled();
- mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
+ mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
}
}
@@ -4886,7 +4900,7 @@
// see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
// the underlyingNetworks list.
if (underlyingNetworks == null) {
- NetworkAgentInfo defaultNai = getDefaultNetwork();
+ final NetworkAgentInfo defaultNai = getDefaultNetwork();
if (defaultNai != null) {
underlyingNetworks = new Network[] { defaultNai.network };
}
@@ -5495,6 +5509,8 @@
*/
@VisibleForTesting
protected class NetworkRequestInfo implements IBinder.DeathRecipient {
+ // The requests to be satisfied in priority order. Non-multilayer requests will only have a
+ // single NetworkRequest in mRequests.
final List<NetworkRequest> mRequests;
// mSatisfier and mActiveRequest rely on one another therefore set them together.
@@ -6040,11 +6056,13 @@
@GuardedBy("mBlockedAppUids")
private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
+ // The always-on request for an Internet-capable network that apps without a specific default
+ // fall back to.
@NonNull
- private final NetworkRequest mDefaultRequest;
- // The NetworkAgentInfo currently satisfying the default request, if any.
- @Nullable
- private volatile NetworkAgentInfo mDefaultNetworkNai = null;
+ private final NetworkRequestInfo mDefaultRequest;
+ // Collection of NetworkRequestInfo's used for default networks.
+ @NonNull
+ private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
// Request used to optionally keep mobile data active even when higher
// priority networks like Wi-Fi are active.
@@ -6057,8 +6075,10 @@
// Request used to optionally keep vehicle internal network always active
private final NetworkRequest mDefaultVehicleRequest;
+ // TODO: b/178729499 update this in favor of a method taking in a UID.
+ // The NetworkAgentInfo currently satisfying the default request, if any.
private NetworkAgentInfo getDefaultNetwork() {
- return mDefaultNetworkNai;
+ return mDefaultRequest.mSatisfier;
}
@Nullable
@@ -7186,14 +7206,41 @@
}
}
- private void makeDefault(@Nullable final NetworkAgentInfo newNetwork) {
- if (DBG) log("Switching to new default network: " + newNetwork);
+ private void processDefaultNetworkChanges(@NonNull final NetworkReassignment changes) {
+ boolean isDefaultChanged = false;
+ for (final NetworkRequestInfo defaultRequestInfo : mDefaultNetworkRequests) {
+ final NetworkReassignment.RequestReassignment reassignment =
+ changes.getReassignment(defaultRequestInfo);
+ if (null == reassignment) {
+ continue;
+ }
+ // reassignment only contains those instances where the satisfying network changed.
+ isDefaultChanged = true;
+ // Notify system services of the new default.
+ makeDefault(defaultRequestInfo, reassignment.mOldNetwork, reassignment.mNewNetwork);
+ }
- mDefaultNetworkNai = newNetwork;
+ if (isDefaultChanged) {
+ // Hold a wakelock for a short time to help apps in migrating to a new default.
+ scheduleReleaseNetworkTransitionWakelock();
+ }
+ }
+
+ private void makeDefault(@NonNull final NetworkRequestInfo nri,
+ @Nullable final NetworkAgentInfo oldDefaultNetwork,
+ @Nullable final NetworkAgentInfo newDefaultNetwork) {
+ if (DBG) {
+ log("Switching to new default network for: " + nri + " using " + newDefaultNetwork);
+ }
try {
- if (null != newNetwork) {
- mNetd.networkSetDefault(newNetwork.network.getNetId());
+ // TODO http://b/176191930 update netd calls in follow-up CL for multinetwork changes.
+ if (mDefaultRequest != nri) {
+ return;
+ }
+
+ if (null != newDefaultNetwork) {
+ mNetd.networkSetDefault(newDefaultNetwork.network.getNetId());
} else {
mNetd.networkClearDefault();
}
@@ -7201,16 +7248,41 @@
loge("Exception setting default network :" + e);
}
- notifyLockdownVpn(newNetwork);
- handleApplyDefaultProxy(null != newNetwork
- ? newNetwork.linkProperties.getHttpProxy() : null);
- updateTcpBufferSizes(null != newNetwork
- ? newNetwork.linkProperties.getTcpBufferSizes() : null);
+ if (oldDefaultNetwork != null) {
+ mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
+ }
+ mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
+ notifyLockdownVpn(newDefaultNetwork);
+ handleApplyDefaultProxy(null != newDefaultNetwork
+ ? newDefaultNetwork.linkProperties.getHttpProxy() : null);
+ updateTcpBufferSizes(null != newDefaultNetwork
+ ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null);
notifyIfacesChangedForNetworkStats();
// Fix up the NetworkCapabilities of any networks that have this network as underlying.
- if (newNetwork != null) {
- propagateUnderlyingNetworkCapabilities(newNetwork.network);
+ if (newDefaultNetwork != null) {
+ propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network);
}
+
+ // Log 0 -> X and Y -> X default network transitions, where X is the new default.
+ final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null;
+ final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0;
+ final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated;
+ final LinkProperties lp = (newDefaultNetwork != null)
+ ? newDefaultNetwork.linkProperties : null;
+ final NetworkCapabilities nc = (newDefaultNetwork != null)
+ ? newDefaultNetwork.networkCapabilities : null;
+
+ final Network prevNetwork = (oldDefaultNetwork != null)
+ ? oldDefaultNetwork.network : null;
+ final int prevScore = (oldDefaultNetwork != null)
+ ? oldDefaultNetwork.getCurrentScore() : 0;
+ final LinkProperties prevLp = (oldDefaultNetwork != null)
+ ? oldDefaultNetwork.linkProperties : null;
+ final NetworkCapabilities prevNc = (oldDefaultNetwork != null)
+ ? oldDefaultNetwork.networkCapabilities : null;
+
+ mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc,
+ prevNetwork, prevScore, prevLp, prevNc);
}
private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
@@ -7462,46 +7534,8 @@
now);
}
- final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork();
- final NetworkRequestInfo defaultRequestInfo = mNetworkRequests.get(mDefaultRequest);
- final NetworkReassignment.RequestReassignment reassignment =
- changes.getReassignment(defaultRequestInfo);
- final NetworkAgentInfo newDefaultNetwork =
- null != reassignment ? reassignment.mNewNetwork : oldDefaultNetwork;
-
- if (oldDefaultNetwork != newDefaultNetwork) {
- if (oldDefaultNetwork != null) {
- mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
- }
- mNetworkActivityTracker.updateDataActivityTracking(
- newDefaultNetwork, oldDefaultNetwork);
- // Notify system services of the new default.
- makeDefault(newDefaultNetwork);
-
- // Log 0 -> X and Y -> X default network transitions, where X is the new default.
- final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null;
- final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0;
- final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated;
- final LinkProperties lp = (newDefaultNetwork != null)
- ? newDefaultNetwork.linkProperties : null;
- final NetworkCapabilities nc = (newDefaultNetwork != null)
- ? newDefaultNetwork.networkCapabilities : null;
-
- final Network prevNetwork = (oldDefaultNetwork != null)
- ? oldDefaultNetwork.network : null;
- final int prevScore = (oldDefaultNetwork != null)
- ? oldDefaultNetwork.getCurrentScore() : 0;
- final LinkProperties prevLp = (oldDefaultNetwork != null)
- ? oldDefaultNetwork.linkProperties : null;
- final NetworkCapabilities prevNc = (oldDefaultNetwork != null)
- ? oldDefaultNetwork.networkCapabilities : null;
-
- mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc,
- prevNetwork, prevScore, prevLp, prevNc);
-
- // Have a new default network, release the transition wakelock in
- scheduleReleaseNetworkTransitionWakelock();
- }
+ // Process default network changes if applicable.
+ processDefaultNetworkChanges(changes);
// Notify requested networks are available after the default net is switched, but
// before LegacyTypeTracker sends legacy broadcasts
@@ -7554,7 +7588,7 @@
notifyNetworkLosing(nai, now);
}
- updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais);
+ updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais);
// Tear down all unneeded networks.
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
@@ -7597,9 +7631,15 @@
}
private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
- @Nullable final NetworkAgentInfo oldDefaultNetwork,
- @Nullable final NetworkAgentInfo newDefaultNetwork,
+ @NonNull final NetworkReassignment changes,
@NonNull final Collection<NetworkAgentInfo> nais) {
+ final NetworkReassignment.RequestReassignment reassignmentOfDefault =
+ changes.getReassignment(mDefaultRequest);
+ final NetworkAgentInfo oldDefaultNetwork =
+ null != reassignmentOfDefault ? reassignmentOfDefault.mOldNetwork : null;
+ final NetworkAgentInfo newDefaultNetwork =
+ null != reassignmentOfDefault ? reassignmentOfDefault.mNewNetwork : null;
+
if (oldDefaultNetwork != newDefaultNetwork) {
// Maintain the illusion : since the legacy API only understands one network at a time,
// if the default network changed, apps should see a disconnected broadcast for the
@@ -7613,7 +7653,8 @@
// network doesn't satisfy the default request any more because it lost a
// capability.
mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
- mLegacyTypeTracker.add(newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
+ mLegacyTypeTracker.add(
+ newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
// If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
// to reflect the NetworkInfo of this new network. This broadcast has to be sent
// after the disconnect broadcasts above, but before the broadcasts sent by the
@@ -7665,7 +7706,7 @@
private void updateInetCondition(NetworkAgentInfo nai) {
// Don't bother updating until we've graduated to validated at least once.
if (!nai.everValidated) return;
- // For now only update icons for default connection.
+ // For now only update icons for the default connection.
// TODO: Update WiFi and cellular icons separately. b/17237507
if (!isDefaultNetwork(nai)) return;
@@ -7935,7 +7976,7 @@
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
}
NetworkAgentInfo newDefaultAgent = null;
- if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
+ if (nai.isSatisfyingRequest(mDefaultRequest.mRequests.get(0).requestId)) {
newDefaultAgent = getDefaultNetwork();
if (newDefaultAgent != null) {
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
@@ -7983,8 +8024,8 @@
*/
private Network[] getDefaultNetworks() {
ensureRunningOnConnectivityServiceThread();
- ArrayList<Network> defaultNetworks = new ArrayList<>();
- NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+ final ArrayList<Network> defaultNetworks = new ArrayList<>();
+ final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
if (nai.everConnected && (nai == defaultNetwork || nai.isVPN())) {
defaultNetworks.add(nai.network);
@@ -8036,7 +8077,6 @@
int user = UserHandle.getUserId(mDeps.getCallingUid());
final boolean success;
synchronized (mVpns) {
- throwIfLockdownEnabled();
success = mVpns.get(user).setUnderlyingNetworks(networks);
}
return success;
@@ -8319,7 +8359,7 @@
throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
}
- final int uid = InetDiagMessage.getConnectionOwnerUid(connectionInfo.protocol,
+ final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol,
connectionInfo.local, connectionInfo.remote);
/* Filter out Uids not associated with the VPN. */
diff --git a/services/core/java/com/android/server/LockGuard.java b/services/core/java/com/android/server/LockGuard.java
index 5ce16c4..b894f34 100644
--- a/services/core/java/com/android/server/LockGuard.java
+++ b/services/core/java/com/android/server/LockGuard.java
@@ -22,8 +22,6 @@
import android.util.ArraySet;
import android.util.Slog;
-import com.android.internal.os.BackgroundThread;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -74,8 +72,9 @@
public static final int INDEX_PACKAGES = 3;
public static final int INDEX_STORAGE = 4;
public static final int INDEX_WINDOW = 5;
- public static final int INDEX_ACTIVITY = 6;
- public static final int INDEX_DPMS = 7;
+ public static final int INDEX_PROC = 6;
+ public static final int INDEX_ACTIVITY = 7;
+ public static final int INDEX_DPMS = 8;
private static Object[] sKnownFixed = new Object[INDEX_DPMS + 1];
@@ -229,6 +228,7 @@
case INDEX_PACKAGES: return "PACKAGES";
case INDEX_STORAGE: return "STORAGE";
case INDEX_WINDOW: return "WINDOW";
+ case INDEX_PROC: return "PROCESS";
case INDEX_ACTIVITY: return "ACTIVITY";
case INDEX_DPMS: return "DPMS";
default: return Integer.toString(index);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 8b506ba..41903fc 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -372,10 +372,12 @@
* even from a previous boot.
*/
public void unregisterHealthObserver(PackageHealthObserver observer) {
- synchronized (mLock) {
- mAllObservers.remove(observer.getName());
- }
- syncState("unregistering observer: " + observer.getName());
+ mLongTaskHandler.post(() -> {
+ synchronized (mLock) {
+ mAllObservers.remove(observer.getName());
+ }
+ syncState("unregistering observer: " + observer.getName());
+ });
}
/**
@@ -982,7 +984,11 @@
if (!DeviceConfig.NAMESPACE_ROLLBACK.equals(properties.getNamespace())) {
return;
}
- updateConfigs();
+ try {
+ updateConfigs();
+ } catch (Exception ignore) {
+ Slog.w(TAG, "Failed to reload device config changes");
+ }
});
}
@@ -990,7 +996,8 @@
* Health check is enabled or disabled after reading the flags
* from DeviceConfig.
*/
- private void updateConfigs() {
+ @VisibleForTesting
+ void updateConfigs() {
synchronized (mLock) {
mTriggerFailureCount = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ROLLBACK,
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index a08d066..e96fd39 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -32,6 +32,7 @@
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
+import android.net.NetworkStack;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.net.TestNetworkInterface;
@@ -48,6 +49,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.NetdUtils;
import java.io.UncheckedIOException;
import java.net.Inet4Address;
@@ -317,10 +319,10 @@
}
try {
- // This requires NETWORK_STACK privileges.
final long token = Binder.clearCallingIdentity();
try {
- mNMS.setInterfaceUp(iface);
+ NetworkStack.checkNetworkStackPermission(mContext);
+ NetdUtils.setInterfaceUp(mNetd, iface);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 8562b0d..916bec2 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -27,10 +27,12 @@
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -288,8 +290,15 @@
public Vcn newVcn(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
- @NonNull VcnConfig config) {
- return new Vcn(vcnContext, subscriptionGroup, config);
+ @NonNull VcnConfig config,
+ @NonNull TelephonySubscriptionSnapshot snapshot) {
+ return new Vcn(vcnContext, subscriptionGroup, config, snapshot);
+ }
+
+ /** Gets the subId indicated by the given {@link WifiInfo}. */
+ public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
+ // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
}
@@ -374,6 +383,7 @@
// delay)
for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
final VcnConfig config = mConfigs.get(entry.getKey());
+
if (config == null
|| !snapshot.packageHasPermissionsForSubscriptionGroup(
entry.getKey(), config.getProvisioningPackageName())) {
@@ -387,10 +397,13 @@
// correct instance is torn down. This could happen as a result of a
// Carrier App manually removing/adding a VcnConfig.
if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
- mVcns.remove(uuidToTeardown).teardownAsynchronously();
+ stopVcnLocked(uuidToTeardown);
}
}
}, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+ } else {
+ // If this VCN's status has not changed, update it with the new snapshot
+ entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
}
}
}
@@ -398,14 +411,39 @@
}
@GuardedBy("mLock")
+ private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) {
+ final Vcn vcnToTeardown = mVcns.remove(uuidToTeardown);
+ if (vcnToTeardown == null) {
+ return;
+ }
+
+ vcnToTeardown.teardownAsynchronously();
+
+ // Now that the VCN is removed, notify all registered listeners to refresh their
+ // UnderlyingNetworkPolicy.
+ notifyAllPolicyListenersLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void notifyAllPolicyListenersLocked() {
+ for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
+ Binder.withCleanCallingIdentity(() -> policyListener.mListener.onPolicyChanged());
+ }
+ }
+
+ @GuardedBy("mLock")
private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);
// TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
// VCN.
- final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
+ final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot);
mVcns.put(subscriptionGroup, newInstance);
+
+ // Now that a new VCN has started, notify all registered listeners to refresh their
+ // UnderlyingNetworkPolicy.
+ notifyAllPolicyListenersLocked();
}
@GuardedBy("mLock")
@@ -468,9 +506,7 @@
synchronized (mLock) {
mConfigs.remove(subscriptionGroup);
- if (mVcns.containsKey(subscriptionGroup)) {
- mVcns.remove(subscriptionGroup).teardownAsynchronously();
- }
+ stopVcnLocked(subscriptionGroup);
writeConfigsToDiskLocked();
}
@@ -582,8 +618,36 @@
"Must have permission NETWORK_FACTORY or be the SystemServer to get underlying"
+ " Network policies");
- // TODO(b/175914059): implement policy generation once VcnManagementService is able to
- // determine policies
+ // Defensive copy in case this call is in-process and the given NetworkCapabilities mutates
+ networkCapabilities = new NetworkCapabilities(networkCapabilities);
+
+ int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && networkCapabilities.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
+ TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ (TelephonyNetworkSpecifier) networkCapabilities.getNetworkSpecifier();
+ subId = telephonyNetworkSpecifier.getSubscriptionId();
+ } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+ && networkCapabilities.getTransportInfo() instanceof WifiInfo) {
+ WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+ subId = mDeps.getSubIdForWifiInfo(wifiInfo);
+ }
+
+ boolean isVcnManagedNetwork = false;
+ if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ synchronized (mLock) {
+ ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);
+
+ // TODO(b/178140910): only mark the Network as VCN-managed if not in safe mode
+ if (mVcns.containsKey(subGroup)) {
+ isVcnManagedNetwork = true;
+ }
+ }
+ }
+ if (isVcnManagedNetwork) {
+ networkCapabilities.removeCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
}
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index eca1dfa..6a816af 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -22,12 +22,20 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.vibrator.IVibrator;
+import android.os.BatteryStats;
+import android.os.Binder;
import android.os.CombinedVibrationEffect;
+import android.os.ExternalVibration;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IExternalVibratorService;
import android.os.IVibratorManagerService;
+import android.os.IVibratorStateListener;
import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.Trace;
@@ -37,12 +45,17 @@
import android.os.VibratorInfo;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.DumpUtils;
+import com.android.server.vibrator.InputDeviceDelegate;
import com.android.server.vibrator.Vibration;
import com.android.server.vibrator.VibrationScaler;
import com.android.server.vibrator.VibrationSettings;
+import com.android.server.vibrator.VibrationThread;
import com.android.server.vibrator.VibratorController;
import libcore.util.NativeAllocationRegistry;
@@ -50,7 +63,11 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -83,18 +100,32 @@
}
}
+ // Used to generate globally unique vibration ids.
+ private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
+
private final Object mLock = new Object();
private final Context mContext;
+ private final PowerManager.WakeLock mWakeLock;
+ private final IBatteryStats mBatteryStatsService;
private final Handler mHandler;
private final AppOpsManager mAppOps;
private final NativeWrapper mNativeWrapper;
+ private final VibratorManagerRecords mVibratorManagerRecords;
private final int[] mVibratorIds;
private final SparseArray<VibratorController> mVibrators;
+ private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
+ @GuardedBy("mLock")
+ private VibrationThread mCurrentVibration;
+ @GuardedBy("mLock")
+ private VibrationThread mNextVibration;
+ @GuardedBy("mLock")
+ private ExternalVibrationHolder mCurrentExternalVibration;
private VibrationSettings mVibrationSettings;
private VibrationScaler mVibrationScaler;
+ private InputDeviceDelegate mInputDeviceDelegate;
static native long nativeInit();
@@ -109,8 +140,19 @@
mNativeWrapper = injector.getNativeWrapper();
mNativeWrapper.init();
+ int dumpLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_previousVibrationsDumpLimit);
+ mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+
+ mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+
mAppOps = mContext.getSystemService(AppOpsManager.class);
+ PowerManager pm = context.getSystemService(PowerManager.class);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
+ mWakeLock.setReferenceCounted(true);
+
int[] vibratorIds = mNativeWrapper.getVibratorIds();
if (vibratorIds == null) {
mVibratorIds = new int[0];
@@ -134,6 +176,7 @@
try {
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+ mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
mVibrationSettings.addListener(this::updateServiceState);
@@ -145,6 +188,11 @@
}
@Override // Binder call
+ public int[] getVibratorIds() {
+ return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+ }
+
+ @Override // Binder call
@Nullable
public VibratorInfo getVibratorInfo(int vibratorId) {
VibratorController controller = mVibrators.get(vibratorId);
@@ -152,8 +200,37 @@
}
@Override // Binder call
- public int[] getVibratorIds() {
- return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+ public boolean isVibrating(int vibratorId) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+ "isVibrating");
+ VibratorController controller = mVibrators.get(vibratorId);
+ return controller != null && controller.isVibrating();
+ }
+
+ @Override // Binder call
+ public boolean registerVibratorStateListener(int vibratorId, IVibratorStateListener listener) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+ "registerVibratorStateListener");
+ VibratorController controller = mVibrators.get(vibratorId);
+ if (controller == null) {
+ return false;
+ }
+ return controller.registerVibratorStateListener(listener);
+ }
+
+ @Override // Binder call
+ public boolean unregisterVibratorStateListener(int vibratorId,
+ IVibratorStateListener listener) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+ "unregisterVibratorStateListener");
+ VibratorController controller = mVibrators.get(vibratorId);
+ if (controller == null) {
+ return false;
+ }
+ return controller.unregisterVibratorStateListener(listener);
}
@Override // Binder call
@@ -161,9 +238,10 @@
@Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attrs) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
try {
- if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
- throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
- }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE_ALWAYS_ON,
+ "setAlwaysOnEffect");
+
if (effect == null) {
synchronized (mLock) {
mAlwaysOnEffects.delete(alwaysOnId);
@@ -200,12 +278,113 @@
@Override // Binder call
public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
- throw new UnsupportedOperationException("Not implemented");
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
+ try {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
+
+ if (token == null) {
+ Slog.e(TAG, "token must not be null");
+ return;
+ }
+ enforceUpdateAppOpsStatsPermission(uid);
+ if (!isEffectValid(effect)) {
+ return;
+ }
+ effect = fixupVibrationEffect(effect);
+ attrs = fixupVibrationAttributes(attrs);
+ Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
+ uid, opPkg, reason);
+
+ synchronized (mLock) {
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
+ if (ignoreStatus != null) {
+ endVibrationLocked(vib, ignoreStatus);
+ return;
+ }
+
+ VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
+ mBatteryStatsService, mVibrationCallbacks);
+
+ ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
+ if (ignoreStatus != null) {
+ endVibrationLocked(vib, ignoreStatus);
+ return;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mCurrentVibration != null) {
+ mCurrentVibration.cancel();
+ }
+ Vibration.Status status = startVibrationLocked(vibThread);
+ if (status != Vibration.Status.RUNNING) {
+ endVibrationLocked(vib, status);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
}
@Override // Binder call
public void cancelVibrate(IBinder token) {
- throw new UnsupportedOperationException("Not implemented");
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE,
+ "cancelVibrate");
+
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling vibration.");
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mNextVibration = null;
+ if (mCurrentVibration != null
+ && mCurrentVibration.getVibration().token == token) {
+ mCurrentVibration.cancel();
+ }
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.end(Vibration.Status.CANCELLED);
+ mVibratorManagerRecords.record(mCurrentExternalVibration);
+ mCurrentExternalVibration.externalVibration.mute();
+ mCurrentExternalVibration = null;
+ // TODO(b/167946816): set external control to false
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ final long ident = Binder.clearCallingIdentity();
+
+ boolean isDumpProto = false;
+ for (String arg : args) {
+ if (arg.equals("--proto")) {
+ isDumpProto = true;
+ }
+ }
+ try {
+ if (isDumpProto) {
+ mVibratorManagerRecords.dumpProto(fd);
+ } else {
+ mVibratorManagerRecords.dumpText(pw);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
@@ -214,11 +393,24 @@
new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
}
- private void updateServiceState() {
+ @VisibleForTesting
+ void updateServiceState() {
synchronized (mLock) {
+ boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
+ mVibrationSettings.shouldVibrateInputDevices());
+
for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i));
}
+
+ if (mCurrentVibration == null) {
+ return;
+ }
+
+ if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
+ mCurrentVibration.getVibration().attrs.getUsage())) {
+ mCurrentVibration.cancel();
+ }
}
}
@@ -230,9 +422,9 @@
if (vibrator == null) {
continue;
}
- Vibration.Status ignoredStatus = shouldIgnoreVibrationLocked(
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
vib.uid, vib.opPkg, vib.attrs);
- if (ignoredStatus == null) {
+ if (ignoreStatus == null) {
effect = mVibrationScaler.scale(effect, vib.attrs.getUsage());
} else {
// Vibration should not run, use null effect to remove registered effect.
@@ -242,6 +434,135 @@
}
}
+ @GuardedBy("mLock")
+ private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
+ try {
+ Vibration vib = vibThread.getVibration();
+ vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
+
+ boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
+ vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
+
+ if (inputDevicesAvailable) {
+ return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
+ }
+
+ if (mCurrentVibration == null) {
+ return startVibrationThreadLocked(vibThread);
+ }
+
+ mNextVibration = vibThread;
+ return Vibration.Status.RUNNING;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
+ try {
+ Vibration vib = vibThread.getVibration();
+ int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
+ switch (mode) {
+ case AppOpsManager.MODE_ALLOWED:
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ mCurrentVibration = vibThread;
+ mCurrentVibration.start();
+ return Vibration.Status.RUNNING;
+ case AppOpsManager.MODE_ERRORED:
+ Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
+ return Vibration.Status.IGNORED_ERROR_APP_OPS;
+ default:
+ return Vibration.Status.IGNORED_APP_OPS;
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void endVibrationLocked(Vibration vib, Vibration.Status status) {
+ vib.end(status);
+ mVibratorManagerRecords.record(vib);
+ }
+
+ @GuardedBy("mLock")
+ private void reportFinishedVibrationLocked(Vibration.Status status) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ try {
+ Vibration vib = mCurrentVibration.getVibration();
+ mCurrentVibration = null;
+ endVibrationLocked(vib, status);
+ finishAppOpModeLocked(vib.uid, vib.opPkg);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private void onVibrationComplete(int vibratorId, long vibrationId) {
+ synchronized (mLock) {
+ if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ + " complete, notifying thread");
+ }
+ mCurrentVibration.vibratorComplete(vibratorId);
+ }
+ }
+ }
+
+ /**
+ * Check if given vibration should be ignored in favour of one of the vibrations currently
+ * running on the same vibrators.
+ *
+ * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
+ if (vibThread.getVibration().isRepeating()) {
+ // Repeating vibrations always take precedence.
+ return null;
+ }
+ if (mCurrentVibration != null && mCurrentVibration.getVibration().isRepeating()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of previous alarm vibration");
+ }
+ return Vibration.Status.IGNORED_FOR_ALARM;
+ }
+ return null;
+ }
+
+ /**
+ * Check if given vibration should be ignored by this service.
+ *
+ * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+ * @see #shouldIgnoreVibrationLocked(int, String, VibrationAttributes)
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Vibration.Status shouldIgnoreVibrationLocked(Vibration vib) {
+ // If something has external control of the vibrator, assume that it's more important.
+ if (mCurrentExternalVibration != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+ }
+ return Vibration.Status.IGNORED_FOR_EXTERNAL;
+ }
+
+ if (!mVibrationSettings.shouldVibrateForUid(vib.uid, vib.attrs.getUsage())) {
+ Slog.e(TAG, "Ignoring incoming vibration as process with"
+ + " uid= " + vib.uid + " is background,"
+ + " attrs= " + vib.attrs);
+ return Vibration.Status.IGNORED_BACKGROUND;
+ }
+
+ return shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+ }
+
/**
* Check if a vibration with given {@code uid}, {@code opPkg} and {@code attrs} should be
* ignored by this service.
@@ -271,7 +592,7 @@
return Vibration.Status.IGNORED_RINGTONE;
}
- int mode = getAppOpMode(uid, opPkg, attrs);
+ int mode = checkAppOpModeLocked(uid, opPkg, attrs);
if (mode != AppOpsManager.MODE_ALLOWED) {
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
@@ -290,21 +611,49 @@
* Check which mode should be set for a vibration with given {@code uid}, {@code opPkg} and
* {@code attrs}. This will return one of the AppOpsManager.MODE_*.
*/
- private int getAppOpMode(int uid, String opPkg, VibrationAttributes attrs) {
+ @GuardedBy("mLock")
+ private int checkAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
attrs.getAudioUsage(), uid, opPkg);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg);
- }
- if (mode == AppOpsManager.MODE_IGNORED
- && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+ int fixedMode = fixupAppOpModeLocked(mode, attrs);
+ if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) {
// If we're just ignoring the vibration op then this is set by DND and we should ignore
// if we're asked to bypass. AppOps won't be able to record this operation, so make
// sure we at least note it in the logs for debugging.
Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
- mode = AppOpsManager.MODE_ALLOWED;
}
- return mode;
+ return fixedMode;
+ }
+
+ /** Start an operation in {@link AppOpsManager}, if allowed. */
+ @GuardedBy("mLock")
+ private int startAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
+ return fixupAppOpModeLocked(
+ mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg), attrs);
+ }
+
+ /**
+ * Finish a previously started operation in {@link AppOpsManager}. This will be a noop if no
+ * operation with same uid was previously started.
+ */
+ @GuardedBy("mLock")
+ private void finishAppOpModeLocked(int uid, String opPkg) {
+ mAppOps.finishOp(AppOpsManager.OP_VIBRATE, uid, opPkg);
+ }
+
+ /**
+ * Enforces {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} to incoming UID if it's
+ * different from the calling UID.
+ */
+ private void enforceUpdateAppOpsStatsPermission(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
}
/**
@@ -330,6 +679,49 @@
}
/**
+ * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
+ * VibrationSettings#getFallbackEffect}.
+ */
+ private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
+ if (effect instanceof CombinedVibrationEffect.Mono) {
+ return CombinedVibrationEffect.createSynced(
+ fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
+ } else if (effect instanceof CombinedVibrationEffect.Stereo) {
+ CombinedVibrationEffect.SyncedCombination combination =
+ CombinedVibrationEffect.startSynced();
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibrationEffect.Stereo) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
+ }
+ return combination.combine();
+ } else if (effect instanceof CombinedVibrationEffect.Sequential) {
+ CombinedVibrationEffect.SequentialCombination combination =
+ CombinedVibrationEffect.startSequential();
+ List<CombinedVibrationEffect> effects =
+ ((CombinedVibrationEffect.Sequential) effect).getEffects();
+ for (CombinedVibrationEffect e : effects) {
+ combination.addNext(fixupVibrationEffect(e));
+ }
+ return combination.combine();
+ }
+ return effect;
+ }
+
+ private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.Prebaked
+ && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
+ VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
+ if (fallback != null) {
+ return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
+ fallback);
+ }
+ }
+ return effect;
+ }
+
+ /**
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
*/
@@ -388,6 +780,19 @@
}
}
+ /**
+ * Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
+ * allow bypassing {@link AppOpsManager} checks.
+ */
+ @GuardedBy("mLock")
+ private int fixupAppOpModeLocked(int mode, VibrationAttributes attrs) {
+ if (mode == AppOpsManager.MODE_IGNORED
+ && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ return mode;
+ }
+
private boolean hasPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
@@ -428,6 +833,42 @@
}
/**
+ * Implementation of {@link VibrationThread.VibrationCallbacks} that controls synced vibrations
+ * and reports them when finished.
+ */
+ private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
+
+ @Override
+ public void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds) {
+ // TODO(b/167946816): call IVibratorManager to prepare
+ }
+
+ @Override
+ public void triggerSyncedVibration(long vibrationId) {
+ // TODO(b/167946816): call IVibratorManager to trigger
+ }
+
+ @Override
+ public void onVibrationEnded(long vibrationId, Vibration.Status status) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration " + vibrationId + " thread finished with status " + status);
+ }
+ synchronized (mLock) {
+ if (mCurrentVibration != null
+ && mCurrentVibration.getVibration().id == vibrationId) {
+ reportFinishedVibrationLocked(status);
+
+ if (mNextVibration != null) {
+ VibrationThread vibThread = mNextVibration;
+ mNextVibration = null;
+ startVibrationThreadLocked(vibThread);
+ }
+ }
+ }
+ }
+ }
+
+ /**
* Implementation of {@link VibratorController.OnVibrationCompleteListener} with a weak
* reference to this service.
*/
@@ -443,7 +884,7 @@
public void onComplete(int vibratorId, long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
- // TODO(b/159207608): finish vibration if all vibrators finished for this vibration
+ service.onVibrationComplete(vibratorId, vibrationId);
}
}
}
@@ -469,6 +910,41 @@
}
}
+ /** Holder for a {@link ExternalVibration}. */
+ private final class ExternalVibrationHolder {
+
+ public final ExternalVibration externalVibration;
+ public int scale;
+
+ private final long mStartTimeDebug;
+ private long mEndTimeDebug;
+ private Vibration.Status mStatus;
+
+ private ExternalVibrationHolder(ExternalVibration externalVibration) {
+ this.externalVibration = externalVibration;
+ this.scale = IExternalVibratorService.SCALE_NONE;
+ mStartTimeDebug = System.currentTimeMillis();
+ mStatus = Vibration.Status.RUNNING;
+ }
+
+ public void end(Vibration.Status status) {
+ if (mStatus != Vibration.Status.RUNNING) {
+ // Vibration already ended, keep first ending status set and ignore this one.
+ return;
+ }
+ mStatus = status;
+ mEndTimeDebug = System.currentTimeMillis();
+ }
+
+ public Vibration.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfo(
+ mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
+ scale, externalVibration.getVibrationAttributes(),
+ externalVibration.getUid(), externalVibration.getPackage(),
+ /* reason= */ null, mStatus);
+ }
+ }
+
/** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
@VisibleForTesting
public static class NativeWrapper {
@@ -494,8 +970,158 @@
}
}
+ /** Keep records of vibrations played and provide debug information for this service. */
+ private final class VibratorManagerRecords {
+ @GuardedBy("mLock")
+ private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
+ new SparseArray<>();
+ @GuardedBy("mLock")
+ private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
+ new LinkedList<>();
+ private final int mPreviousVibrationsLimit;
+
+ private VibratorManagerRecords(int limit) {
+ mPreviousVibrationsLimit = limit;
+ }
+
+ @GuardedBy("mLock")
+ void record(Vibration vib) {
+ int usage = vib.attrs.getUsage();
+ if (!mPreviousVibrations.contains(usage)) {
+ mPreviousVibrations.put(usage, new LinkedList<>());
+ }
+ record(mPreviousVibrations.get(usage), vib.getDebugInfo());
+ }
+
+ @GuardedBy("mLock")
+ void record(ExternalVibrationHolder vib) {
+ record(mPreviousExternalVibrations, vib.getDebugInfo());
+ }
+
+ @GuardedBy("mLock")
+ void record(LinkedList<Vibration.DebugInfo> records, Vibration.DebugInfo info) {
+ if (records.size() > mPreviousVibrationsLimit) {
+ records.removeFirst();
+ }
+ records.addLast(info);
+ }
+
+ void dumpText(PrintWriter pw) {
+ pw.println("Vibrator Manager Service:");
+ synchronized (mLock) {
+ pw.println(" mVibratorControllers:");
+ for (int i = 0; i < mVibrators.size(); i++) {
+ pw.println(" " + mVibrators.valueAt(i));
+ }
+ pw.println();
+ pw.println(" mCurrentVibration:");
+ pw.println(" " + mCurrentVibration == null
+ ? null : mCurrentVibration.getVibration().getDebugInfo());
+ pw.println(" mNextVibration:");
+ pw.println(" " + mNextVibration == null
+ ? null : mNextVibration.getVibration().getDebugInfo());
+ pw.println(" mCurrentExternalVibration:");
+ pw.println(" " + mCurrentExternalVibration == null
+ ? null : mCurrentExternalVibration.getDebugInfo());
+ pw.println();
+ pw.println(" mVibrationSettings=" + mVibrationSettings);
+ for (int i = 0; i < mPreviousVibrations.size(); i++) {
+ pw.println();
+ pw.print(" Previous vibrations for usage ");
+ pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
+ pw.println(":");
+ for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+ pw.println(" " + info);
+ }
+ }
+
+ pw.println(" Previous external vibrations:");
+ for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+ pw.println(" " + info);
+ }
+ }
+ }
+
+ synchronized void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ synchronized (mLock) {
+ mVibrationSettings.dumpProto(proto);
+ if (mCurrentVibration != null) {
+ mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+ VibratorServiceDumpProto.CURRENT_VIBRATION);
+ }
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+ VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+ }
+
+ boolean isVibrating = false;
+ boolean isUnderExternalControl = false;
+ for (int i = 0; i < mVibrators.size(); i++) {
+ isVibrating |= mVibrators.valueAt(i).isVibrating();
+ isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
+ }
+ proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
+ proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+ isUnderExternalControl);
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_RINGTONE)) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_NOTIFICATION)) {
+ info.dumpProto(proto,
+ VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_ALARM)) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_UNKNOWN)) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ }
+ }
+ proto.flush();
+ }
+ }
+
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
private final class VibratorManagerShellCommand extends ShellCommand {
+ public static final String SHELL_PACKAGE_NAME = "com.android.shell";
+
+ private final class CommonOptions {
+ public boolean force = false;
+ public String description = "Shell command";
+
+ CommonOptions() {
+ String nextArg;
+ while ((nextArg = peekNextArg()) != null) {
+ switch (nextArg) {
+ case "-f":
+ getNextArgRequired(); // consume the -f argument;
+ force = true;
+ break;
+ case "-d":
+ getNextArgRequired(); // consume the -d argument;
+ description = getNextArgRequired();
+ break;
+ default:
+ // Not a common option, finish reading.
+ return;
+ }
+ }
+ }
+ }
private final IBinder mToken;
@@ -505,10 +1131,27 @@
@Override
public int onCommand(String cmd) {
- if ("list".equals(cmd)) {
- return runListVibrators();
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onCommand " + cmd);
+ try {
+ if ("list".equals(cmd)) {
+ return runListVibrators();
+ }
+ if ("synced".equals(cmd)) {
+ return runMono();
+ }
+ if ("combined".equals(cmd)) {
+ return runStereo();
+ }
+ if ("sequential".equals(cmd)) {
+ return runSequential();
+ }
+ if ("cancel".equals(cmd)) {
+ return runCancel();
+ }
+ return handleDefaultCommands(cmd);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
- return handleDefaultCommands(cmd);
}
private int runListVibrators() {
@@ -525,6 +1168,157 @@
}
}
+ private int runMono() {
+ CommonOptions commonOptions = new CommonOptions();
+ VibrationEffect effect = nextEffect();
+ if (effect == null) {
+ return 0;
+ }
+
+ CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+ vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combinedEffect, attrs,
+ commonOptions.description, mToken);
+ return 0;
+ }
+
+ private int runStereo() {
+ CommonOptions commonOptions = new CommonOptions();
+ CombinedVibrationEffect.SyncedCombination combination =
+ CombinedVibrationEffect.startSynced();
+ while ("-v".equals(getNextOption())) {
+ int vibratorId = Integer.parseInt(getNextArgRequired());
+ VibrationEffect effect = nextEffect();
+ if (effect != null) {
+ combination.addVibrator(vibratorId, effect);
+ }
+ }
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+ vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+ commonOptions.description, mToken);
+ return 0;
+ }
+
+ private int runSequential() {
+ CommonOptions commonOptions = new CommonOptions();
+
+ CombinedVibrationEffect.SequentialCombination combination =
+ CombinedVibrationEffect.startSequential();
+ while ("-v".equals(getNextOption())) {
+ int vibratorId = Integer.parseInt(getNextArgRequired());
+ int delay = 0;
+ if ("-w".equals(getNextOption())) {
+ delay = Integer.parseInt(getNextArgRequired());
+ }
+ VibrationEffect effect = nextEffect();
+ if (effect != null) {
+ combination.addNext(vibratorId, effect, delay);
+ }
+ }
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+ vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+ commonOptions.description, mToken);
+ return 0;
+ }
+
+ private int runCancel() {
+ cancelVibrate(mToken);
+ return 0;
+ }
+
+ @Nullable
+ private VibrationEffect nextEffect() {
+ String effectType = getNextArgRequired();
+ if ("oneshot".equals(effectType)) {
+ return nextOneShot();
+ }
+ if ("waveform".equals(effectType)) {
+ return nextWaveform();
+ }
+ if ("prebaked".equals(effectType)) {
+ return nextPrebaked();
+ }
+ if ("composed".equals(effectType)) {
+ return nextComposed();
+ }
+ return null;
+ }
+
+ private VibrationEffect nextOneShot() {
+ boolean hasAmplitude = "-a".equals(getNextOption());
+ long duration = Long.parseLong(getNextArgRequired());
+ int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
+ : VibrationEffect.DEFAULT_AMPLITUDE;
+ return VibrationEffect.createOneShot(duration, amplitude);
+ }
+
+ private VibrationEffect nextWaveform() {
+ boolean hasAmplitudes = false;
+ int repeat = -1;
+
+ String nextOption = getNextOption();
+ while (nextOption != null) {
+ if ("-a".equals(nextOption)) {
+ hasAmplitudes = true;
+ } else if ("-r".equals(nextOption)) {
+ repeat = Integer.parseInt(getNextArgRequired());
+ }
+ nextOption = getNextOption();
+ }
+ List<Long> durations = new ArrayList<>();
+ List<Integer> amplitudes = new ArrayList<>();
+
+ String nextArg;
+ while ((nextArg = peekNextArg()) != null && !"-v".equals(nextArg)) {
+ durations.add(Long.parseLong(getNextArgRequired()));
+ if (hasAmplitudes) {
+ amplitudes.add(Integer.parseInt(getNextArgRequired()));
+ }
+ }
+
+ long[] durationArray = durations.stream().mapToLong(Long::longValue).toArray();
+ if (!hasAmplitudes) {
+ return VibrationEffect.createWaveform(durationArray, repeat);
+ }
+
+ int[] amplitudeArray = amplitudes.stream().mapToInt(Integer::intValue).toArray();
+ return VibrationEffect.createWaveform(durationArray, amplitudeArray, repeat);
+ }
+
+ private VibrationEffect nextPrebaked() {
+ boolean shouldFallback = "-b".equals(getNextOption());
+ int effectId = Integer.parseInt(getNextArgRequired());
+ return VibrationEffect.get(effectId, shouldFallback);
+ }
+
+ private VibrationEffect nextComposed() {
+ VibrationEffect.Composition composition = VibrationEffect.startComposition();
+ String nextArg;
+ while ((nextArg = peekNextArg()) != null) {
+ int delay = 0;
+ if ("-w".equals(nextArg)) {
+ getNextArgRequired(); // consume the -w option
+ delay = Integer.parseInt(getNextArgRequired());
+ } else if ("-v".equals(nextArg)) {
+ // Starting next vibrator, this composed effect if finished.
+ break;
+ }
+ int primitiveId = Integer.parseInt(getNextArgRequired());
+ composition.addPrimitive(primitiveId, /* scale= */ 1f, delay);
+ }
+ return composition.compose();
+ }
+
+ private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
+ final int flags =
+ commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0;
+ return new VibrationAttributes.Builder()
+ .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
+ // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
+ .setUsage(VibrationAttributes.USAGE_TOUCH)
+ .build();
+ }
+
@Override
public void onHelp() {
try (PrintWriter pw = getOutPrintWriter();) {
@@ -535,6 +1329,49 @@
pw.println(" list");
pw.println(" Prints the id of device vibrators. This does not include any ");
pw.println(" connected input device.");
+ pw.println(" synced [options] <effect>");
+ pw.println(" Vibrates effect on all vibrators in sync.");
+ pw.println(" combined [options] (-v <vibrator-id> <effect>)...");
+ pw.println(" Vibrates different effects on each vibrator in sync.");
+ pw.println(" sequential [options] (-v <vibrator-id> [-w <delay>] <effect>)...");
+ pw.println(" Vibrates different effects on each vibrator in sequence.");
+ pw.println(" cancel");
+ pw.println(" Cancels any active vibration");
+ pw.println("");
+ pw.println("Effect commands:");
+ pw.println(" oneshot [-a] <duration> [<amplitude>]");
+ pw.println(" Vibrates for duration milliseconds; ignored when device is on ");
+ pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting ");
+ pw.println(" will be used to scale amplitude.");
+ pw.println(" If -a is provided, the command accepts a second argument for ");
+ pw.println(" amplitude, in a scale of 1-255.");
+ pw.println(" waveform [-r <index>] [-a] (<duration> [<amplitude>])...");
+ pw.println(" Vibrates for durations and amplitudes in list; ignored when ");
+ pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength ");
+ pw.println(" user setting will be used to scale amplitude.");
+ pw.println(" If -r is provided, the waveform loops back to the specified");
+ pw.println(" index (e.g. 0 loops from the beginning)");
+ pw.println(" If -a is provided, the command accepts duration-amplitude pairs;");
+ pw.println(" otherwise, it accepts durations only and alternates off/on");
+ pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255.");
+ pw.println(" prebaked [-b] <effect-id>");
+ pw.println(" Vibrates with prebaked effect; ignored when device is on DND ");
+ pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
+ pw.println(" will be used to scale amplitude.");
+ pw.println(" If -b is provided, the prebaked fallback effect will be played if");
+ pw.println(" the device doesn't support the given effect-id.");
+ pw.println(" composed [-w <delay>] <primitive-id>...");
+ pw.println(" Vibrates with a composed effect; ignored when device is on DND ");
+ pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
+ pw.println(" will be used to scale primitive intensities.");
+ pw.println(" If -w is provided, the next primitive will be played after the ");
+ pw.println(" specified wait time in milliseconds.");
+ pw.println("");
+ pw.println("Common Options:");
+ pw.println(" -f");
+ pw.println(" Force. Ignore Do Not Disturb setting.");
+ pw.println(" -d <description>");
+ pw.println(" Add description to the vibration.");
pw.println("");
}
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 97e313e..026eb63 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -772,18 +772,7 @@
proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
mVibratorController.isUnderExternalControl());
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH));
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
- proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION));
- proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
- proto.write(VibratorServiceDumpProto.RING_INTENSITY,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
- proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+ mVibrationSettings.dumpProto(proto);
for (Vibration.DebugInfo info : mPreviousRingVibrations) {
info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index d63a6c3..5b50431 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -111,7 +111,6 @@
};
public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
- "android.hardware.audio@2.0::IDevicesFactory",
"android.hardware.audio@4.0::IDevicesFactory",
"android.hardware.audio@5.0::IDevicesFactory",
"android.hardware.audio@6.0::IDevicesFactory",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0c8172d..a4ff230 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -19,6 +19,9 @@
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
import static android.os.Process.NFC_UID;
@@ -48,7 +51,6 @@
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.ApplicationExitInfo;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
@@ -621,14 +623,14 @@
final boolean callerFg;
if (caller != null) {
- final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+ final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
if (callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + callingPid
+ ") when starting service " + service);
}
- callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
+ callerFg = callerApp.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_BACKGROUND;
} else {
callerFg = true;
}
@@ -656,7 +658,7 @@
// If we're starting indirectly (e.g. from PendingIntent), figure out whether
// we're launching into an app in a background state. This keys off of the same
// idleness state tracking as e.g. O+ background service start policy.
- final boolean bgLaunch = !mAm.isUidActiveLocked(r.appInfo.uid);
+ final boolean bgLaunch = !mAm.isUidActiveLOSP(r.appInfo.uid);
// If the app has strict background restrictions, we treat any bg service
// start analogously to the legacy-app forced-restrictions case, regardless
@@ -671,34 +673,29 @@
}
if (fgRequired) {
- if (isFgsBgStart(r.mAllowStartForeground)) {
- if (!r.mLoggedInfoAllowStartForeground) {
- Slog.wtf(TAG, "Background started FGS " + r.mInfoAllowStartForeground);
- r.mLoggedInfoAllowStartForeground = true;
+ logFgsBackgroundStart(r);
+ if (r.mAllowStartForeground == FGS_FEATURE_DENIED && isBgFgsRestrictionEnabled(r)) {
+ String msg = "startForegroundService() not allowed due to "
+ + "mAllowStartForeground false: service "
+ + r.shortInstanceName;
+ Slog.w(TAG, msg);
+ showFgsBgRestrictedNotificationLocked(r);
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+ callingPackage, ActivityManagerService.STOCK_PM_FLAGS,
+ userId);
+ } catch (android.os.RemoteException e) {
+ // pm is in same process, this will never happen.
}
- if (r.mAllowStartForeground == FGS_FEATURE_DENIED && isBgFgsRestrictionEnabled(r)) {
- String msg = "startForegroundService() not allowed due to "
- + "mAllowStartForeground false: service "
- + r.shortInstanceName;
- Slog.w(TAG, msg);
- showFgsBgRestrictedNotificationLocked(r);
- ApplicationInfo aInfo = null;
- try {
- aInfo = AppGlobals.getPackageManager().getApplicationInfo(
- callingPackage, ActivityManagerService.STOCK_PM_FLAGS,
- userId);
- } catch (android.os.RemoteException e) {
- // pm is in same process, this will never happen.
- }
- if (aInfo == null) {
- throw new SecurityException("startServiceLocked failed, "
- + "could not resolve client package " + callingPackage);
- }
- if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, aInfo.uid)) {
- throw new IllegalStateException(msg);
- }
- return null;
+ if (aInfo == null) {
+ throw new SecurityException("startServiceLocked failed, "
+ + "could not resolve client package " + callingPackage);
}
+ if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, aInfo.uid)) {
+ throw new IllegalStateException(msg);
+ }
+ return null;
}
}
@@ -732,7 +729,7 @@
if (forcedStandby || (!r.startRequested && !fgRequired)) {
// Before going further -- if this app is not allowed to start services in the
// background, then at this point we aren't going to let it period.
- final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
+ final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
@@ -757,7 +754,7 @@
}
// This app knows it is in the new model where this operation is not
// allowed, so tell it what has happened.
- UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid);
+ UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(r.appInfo.uid);
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
@@ -830,7 +827,7 @@
if (!callerFg && !fgRequired && r.app == null
&& mAm.mUserController.hasStartedUserState(r.userId)) {
ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
- if (proc == null || proc.getCurProcState() > ActivityManager.PROCESS_STATE_RECEIVER) {
+ if (proc == null || proc.mState.getCurProcState() > PROCESS_STATE_RECEIVER) {
// If this is not coming from a foreground caller, then we may want
// to delay the start if there are already other background services
// that are starting. This is to avoid process start spam when lots
@@ -858,7 +855,7 @@
}
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Not delaying: " + r);
addToStarting = true;
- } else if (proc.getCurProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
+ } else if (proc.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
// We slightly loosen when we will enqueue this new service as a background
// starting service we are waiting for, to also include processes that are
// currently running other services or receivers.
@@ -867,9 +864,9 @@
"Not delaying, but counting as bg: " + r);
} else if (DEBUG_DELAYED_STARTS) {
StringBuilder sb = new StringBuilder(128);
- sb.append("Not potential delay (state=").append(proc.getCurProcState())
- .append(' ').append(proc.adjType);
- String reason = proc.makeAdjReason();
+ sb.append("Not potential delay (state=").append(proc.mState.getCurProcState())
+ .append(' ').append(proc.mState.getAdjType());
+ String reason = proc.mState.makeAdjReason();
if (reason != null) {
sb.append(' ');
sb.append(reason);
@@ -1164,7 +1161,7 @@
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopService: " + service
+ " type=" + resolvedType);
- final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+ final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
if (caller != null && callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
@@ -1200,7 +1197,7 @@
for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
if (service.appInfo.uid == uid && service.startRequested) {
- if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,
+ if (mAm.getAppStartModeLOSP(service.appInfo.uid, service.packageName,
service.appInfo.targetSdkVersion, -1, false, false, false)
!= ActivityManager.APP_START_MODE_NORMAL) {
if (stopping == null) {
@@ -1630,13 +1627,13 @@
}
void foregroundServiceProcStateChangedLocked(UidRecord uidRec) {
- ServiceMap smap = mServiceMap.get(UserHandle.getUserId(uidRec.uid));
+ ServiceMap smap = mServiceMap.get(UserHandle.getUserId(uidRec.getUid()));
if (smap != null) {
boolean changed = false;
for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) {
ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j);
- if (active.mUid == uidRec.uid) {
- if (uidRec.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) {
+ if (active.mUid == uidRec.getUid()) {
+ if (uidRec.getCurProcState() <= PROCESS_STATE_TOP) {
if (!active.mAppOnTop) {
active.mAppOnTop = true;
changed = true;
@@ -1655,7 +1652,7 @@
}
private boolean appIsTopLocked(int uid) {
- return mAm.getUidState(uid) <= ActivityManager.PROCESS_STATE_TOP;
+ return mAm.getUidStateLocked(uid) <= PROCESS_STATE_TOP;
}
/**
@@ -1687,13 +1684,13 @@
default:
mAm.enforcePermission(
android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
- r.app.pid, r.appInfo.uid, "startForeground");
+ r.app.getPid(), r.appInfo.uid, "startForeground");
}
} else {
if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
mAm.enforcePermission(
android.Manifest.permission.FOREGROUND_SERVICE,
- r.app.pid, r.appInfo.uid, "startForeground");
+ r.app.getPid(), r.appInfo.uid, "startForeground");
}
int manifestType = r.serviceInfo.getForegroundServiceType();
@@ -1734,6 +1731,7 @@
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
}
+ final ProcessServiceRecord psr = r.app.mServices;
try {
boolean ignoreForeground = false;
final int mode = mAm.getAppOpsManager().checkOpNoThrow(
@@ -1763,30 +1761,24 @@
+ r.shortInstanceName);
// Back off of any foreground expectations around this service, since we've
// just turned down its fg request.
- updateServiceForegroundLocked(r.app, false);
+ updateServiceForegroundLocked(psr, false);
ignoreForeground = true;
}
if (!ignoreForeground) {
- if (isFgsBgStart(r.mAllowStartForeground)) {
- if (!r.mLoggedInfoAllowStartForeground) {
- Slog.wtf(TAG, "Background started FGS "
- + r.mInfoAllowStartForeground);
- r.mLoggedInfoAllowStartForeground = true;
- }
- if (r.mAllowStartForeground == FGS_FEATURE_DENIED
- && isBgFgsRestrictionEnabled(r)) {
- final String msg = "Service.startForeground() not allowed due to "
- + "mAllowStartForeground false: service "
- + r.shortInstanceName;
- Slog.w(TAG, msg);
- showFgsBgRestrictedNotificationLocked(r);
- updateServiceForegroundLocked(r.app, true);
- ignoreForeground = true;
- if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
- r.appInfo.uid)) {
- throw new IllegalStateException(msg);
- }
+ logFgsBackgroundStart(r);
+ if (r.mAllowStartForeground == FGS_FEATURE_DENIED
+ && isBgFgsRestrictionEnabled(r)) {
+ final String msg = "Service.startForeground() not allowed due to "
+ + "mAllowStartForeground false: service "
+ + r.shortInstanceName;
+ Slog.w(TAG, msg);
+ showFgsBgRestrictedNotificationLocked(r);
+ updateServiceForegroundLocked(psr, true);
+ ignoreForeground = true;
+ if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
+ r.appInfo.uid)) {
+ throw new IllegalStateException(msg);
}
}
}
@@ -1813,10 +1805,12 @@
active.mPackageName = r.packageName;
active.mUid = r.appInfo.uid;
active.mShownWhileScreenOn = mScreenOn;
- if (r.app != null && r.app.uidRecord != null) {
- active.mAppOnTop = active.mShownWhileTop =
- r.app.uidRecord.getCurProcState()
- <= ActivityManager.PROCESS_STATE_TOP;
+ if (r.app != null) {
+ final UidRecord uidRec = r.app.getUidRecord();
+ if (uidRec != null) {
+ active.mAppOnTop = active.mShownWhileTop =
+ uidRec.getCurProcState() <= PROCESS_STATE_TOP;
+ }
}
active.mStartTime = active.mStartVisibleTime
= SystemClock.elapsedRealtime();
@@ -1848,7 +1842,7 @@
}
postFgsNotificationLocked(r);
if (r.app != null) {
- updateServiceForegroundLocked(r.app, true);
+ updateServiceForegroundLocked(psr, true);
}
getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
mAm.notifyPackageUse(r.serviceInfo.packageName,
@@ -1900,7 +1894,7 @@
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
if (r.app != null) {
mAm.updateLruProcessLocked(r.app, false, null);
- updateServiceForegroundLocked(r.app, true);
+ updateServiceForegroundLocked(r.app.mServices, true);
}
}
// Leave the time-to-display as already set: re-entering foreground mode will
@@ -2138,7 +2132,7 @@
}
private boolean isNotTop() {
- return mProcessRecord.getCurProcState() != ActivityManager.PROCESS_STATE_TOP;
+ return mProcessRecord.mState.getCurProcState() != PROCESS_STATE_TOP;
}
private void incrementOpCount(int op, boolean allowed) {
@@ -2236,36 +2230,45 @@
}
}
- private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) {
+ private void updateServiceForegroundLocked(ProcessServiceRecord psr, boolean oomAdj) {
boolean anyForeground = false;
int fgServiceTypes = 0;
- for (int i = proc.numberOfRunningServices() - 1; i >= 0; i--) {
- ServiceRecord sr = proc.getRunningServiceAt(i);
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ ServiceRecord sr = psr.getRunningServiceAt(i);
if (sr.isForeground || sr.fgRequired) {
anyForeground = true;
fgServiceTypes |= sr.foregroundServiceType;
}
}
- mAm.updateProcessForegroundLocked(proc, anyForeground, fgServiceTypes, oomAdj);
+ mAm.updateProcessForegroundLocked(psr.mApp, anyForeground, fgServiceTypes, oomAdj);
}
- private void updateAllowlistManagerLocked(ProcessRecord proc) {
- proc.mAllowlistManager = false;
- for (int i = proc.numberOfRunningServices() - 1; i >= 0; i--) {
- ServiceRecord sr = proc.getRunningServiceAt(i);
+ private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
+ psr.mAllowlistManager = false;
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ ServiceRecord sr = psr.getRunningServiceAt(i);
if (sr.whitelistManager) {
- proc.mAllowlistManager = true;
+ psr.mAllowlistManager = true;
break;
}
}
}
- public void updateServiceConnectionActivitiesLocked(ProcessRecord clientProc) {
+ private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
+ final ProcessServiceRecord psr = service.app.mServices;
+ psr.stopService(service);
+ psr.updateBoundClientUids();
+ if (service.whitelistManager) {
+ updateAllowlistManagerLocked(psr);
+ }
+ }
+
+ void updateServiceConnectionActivitiesLocked(ProcessServiceRecord clientPsr) {
ArraySet<ProcessRecord> updatedProcesses = null;
- for (int i = 0; i < clientProc.connections.size(); i++) {
- final ConnectionRecord conn = clientProc.connections.valueAt(i);
+ for (int i = 0; i < clientPsr.numberOfConnections(); i++) {
+ final ConnectionRecord conn = clientPsr.getConnectionAt(i);
final ProcessRecord proc = conn.binding.service.app;
- if (proc == null || proc == clientProc) {
+ if (proc == null || proc == clientPsr.mApp) {
continue;
} else if (updatedProcesses == null) {
updatedProcesses = new ArraySet<>();
@@ -2273,11 +2276,11 @@
continue;
}
updatedProcesses.add(proc);
- updateServiceClientActivitiesLocked(proc, null, false);
+ updateServiceClientActivitiesLocked(proc.mServices, null, false);
}
}
- private boolean updateServiceClientActivitiesLocked(ProcessRecord proc,
+ private boolean updateServiceClientActivitiesLocked(ProcessServiceRecord psr,
ConnectionRecord modCr, boolean updateLru) {
if (modCr != null && modCr.binding.client != null) {
if (!modCr.binding.client.hasActivities()) {
@@ -2288,14 +2291,14 @@
}
boolean anyClientActivities = false;
- for (int i = proc.numberOfRunningServices() - 1; i >= 0 && !anyClientActivities; i--) {
- ServiceRecord sr = proc.getRunningServiceAt(i);
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0 && !anyClientActivities; i--) {
+ ServiceRecord sr = psr.getRunningServiceAt(i);
ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = sr.getConnections();
for (int conni = connections.size() - 1; conni >= 0 && !anyClientActivities; conni--) {
ArrayList<ConnectionRecord> clist = connections.valueAt(conni);
for (int cri=clist.size()-1; cri>=0; cri--) {
ConnectionRecord cr = clist.get(cri);
- if (cr.binding.client == null || cr.binding.client == proc) {
+ if (cr.binding.client == null || cr.binding.client == psr.mApp) {
// Binding to ourself is not interesting.
continue;
}
@@ -2306,10 +2309,10 @@
}
}
}
- if (anyClientActivities != proc.hasClientActivities()) {
- proc.setHasClientActivities(anyClientActivities);
+ if (anyClientActivities != psr.hasClientActivities()) {
+ psr.setHasClientActivities(anyClientActivities);
if (updateLru) {
- mAm.updateLruProcessLocked(proc, anyClientActivities, null);
+ mAm.updateLruProcessLocked(psr.mApp, anyClientActivities, null);
}
return true;
}
@@ -2325,7 +2328,7 @@
+ " flags=0x" + Integer.toHexString(flags));
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
- final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+ final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
if (callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
@@ -2397,7 +2400,8 @@
"BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND");
}
- final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
+ final boolean callerFg = callerApp.mState.getSetSchedGroup()
+ != ProcessList.SCHED_GROUP_BACKGROUND;
final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
@@ -2451,7 +2455,7 @@
}
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
- callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
+ callerApp.mState.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
s.instanceName, s.processName);
// Once the apps have become associated, if one of them is caller is ephemeral
// the target app should now be able to see the calling app
@@ -2469,10 +2473,11 @@
if (activity != null) {
activity.addConnection(c);
}
- b.client.connections.add(c);
+ final ProcessServiceRecord clientPsr = b.client.mServices;
+ clientPsr.addConnection(c);
c.startAssociationIfNeeded();
if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
- b.client.hasAboveClient = true;
+ clientPsr.setHasAboveClient(true);
}
if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) {
s.whitelistManager = true;
@@ -2486,7 +2491,7 @@
}
if (s.app != null) {
- updateServiceClientActivitiesLocked(s.app, c, true);
+ updateServiceClientActivitiesLocked(s.app.mServices, c, true);
}
ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
if (clist == null) {
@@ -2508,17 +2513,18 @@
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, false);
if (s.app != null) {
+ ProcessServiceRecord servicePsr = s.app.mServices;
if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
- s.app.treatLikeActivity = true;
+ servicePsr.setTreatLikeActivity(true);
}
if (s.whitelistManager) {
- s.app.mAllowlistManager = true;
+ servicePsr.mAllowlistManager = true;
}
// This could have made the service more important.
- mAm.updateLruProcessLocked(s.app,
- (callerApp.hasActivitiesOrRecentTasks() && s.app.hasClientActivities())
- || (callerApp.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP
- && (flags & Context.BIND_TREAT_LIKE_ACTIVITY) != 0),
+ mAm.updateLruProcessLocked(s.app, (callerApp.hasActivitiesOrRecentTasks()
+ && servicePsr.hasClientActivities())
+ || (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP
+ && (flags & Context.BIND_TREAT_LIKE_ACTIVITY) != 0),
b.client);
needOomAdj = true;
mAm.enqueueOomAdjTargetLocked(s.app);
@@ -2638,14 +2644,15 @@
final ServiceRecord srec = crec.binding.service;
if (srec != null && (srec.serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
if (srec.app != null) {
+ final ProcessServiceRecord psr = srec.app.mServices;
if (group > 0) {
- srec.app.connectionService = srec;
- srec.app.connectionGroup = group;
- srec.app.connectionImportance = importance;
+ psr.setConnectionService(srec);
+ psr.setConnectionGroup(group);
+ psr.setConnectionImportance(importance);
} else {
- srec.app.connectionService = null;
- srec.app.connectionGroup = 0;
- srec.app.connectionImportance = 0;
+ psr.setConnectionService(null);
+ psr.setConnectionGroup(0);
+ psr.setConnectionImportance(0);
}
} else {
if (group > 0) {
@@ -2683,15 +2690,14 @@
final ProcessRecord app = r.binding.service.app;
if (app != null) {
- if (app.mAllowlistManager) {
- updateAllowlistManagerLocked(app);
+ final ProcessServiceRecord psr = app.mServices;
+ if (psr.mAllowlistManager) {
+ updateAllowlistManagerLocked(psr);
}
// This could have made the service less important.
if ((r.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
- app.treatLikeActivity = true;
- mAm.updateLruProcessLocked(app,
- app.hasClientActivities()
- || app.treatLikeActivity, null);
+ psr.setTreatLikeActivity(true);
+ mAm.updateLruProcessLocked(app, true, null);
}
mAm.enqueueOomAdjTargetLocked(app);
}
@@ -2725,7 +2731,7 @@
boolean inFg = false;
for (int i=b.apps.size()-1; i>=0; i--) {
ProcessRecord client = b.apps.valueAt(i).client;
- if (client != null && client.setSchedGroup
+ if (client != null && client.mState.getSetSchedGroup()
!= ProcessList.SCHED_GROUP_BACKGROUND) {
inFg = true;
break;
@@ -2815,12 +2821,24 @@
r = smap.mServicesByIntent.get(filter);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by intent: " + r);
}
- if (r != null && (r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
- && !callingPackage.equals(r.packageName)) {
- // If an external service is running within its own package, other packages
- // should not bind to that instance.
- r = null;
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+ if (r != null) {
+ // Compared to resolveService below, the ServiceRecord here is retrieved from
+ // ServiceMap so the package visibility doesn't apply to it. We need to filter it.
+ if (mAm.getPackageManagerInternal().filterAppAccess(r.packageName, callingUid,
+ userId)) {
+ Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId
+ + ": not found");
+ return null;
+ }
+ if ((r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
+ && !callingPackage.equals(r.packageName)) {
+ // If an external service is running within its own package, other packages
+ // should not bind to that instance.
+ r = null;
+ if (DEBUG_SERVICE) {
+ Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+ }
+ }
}
if (r == null) {
try {
@@ -3027,7 +3045,7 @@
// happen.)
boolean timeoutNeeded = true;
if ((mAm.mBootPhase < SystemService.PHASE_THIRD_PARTY_APPS_CAN_START)
- && (r.app != null) && (r.app.pid == android.os.Process.myPid())) {
+ && (r.app != null) && (r.app.getPid() == ActivityManagerService.MY_PID)) {
Slog.w(TAG, "Too early to start/bind service in system_server: Phase=" + mAm.mBootPhase
+ " " + r.getComponentName());
@@ -3035,6 +3053,7 @@
}
long now = SystemClock.uptimeMillis();
+ ProcessServiceRecord psr;
if (r.executeNesting == 0) {
r.executeFg = fg;
ServiceState stracker = r.getTracker();
@@ -3042,16 +3061,20 @@
stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
}
if (r.app != null) {
- r.app.executingServices.add(r);
- r.app.execServicesFg |= fg;
- if (timeoutNeeded && r.app.executingServices.size() == 1) {
+ psr = r.app.mServices;
+ psr.startExecutingService(r);
+ psr.setExecServicesFg(psr.shouldExecServicesFg() || fg);
+ if (timeoutNeeded && psr.numberOfExecutingServices() == 1) {
scheduleServiceTimeoutLocked(r.app);
}
}
- } else if (r.app != null && fg && !r.app.execServicesFg) {
- r.app.execServicesFg = true;
- if (timeoutNeeded) {
- scheduleServiceTimeoutLocked(r.app);
+ } else if (r.app != null && fg) {
+ psr = r.app.mServices;
+ if (!psr.shouldExecServicesFg()) {
+ psr.setExecServicesFg(true);
+ if (timeoutNeeded) {
+ scheduleServiceTimeoutLocked(r.app);
+ }
}
}
r.executeFg |= fg;
@@ -3061,7 +3084,7 @@
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
boolean execInFg, boolean rebind) throws TransactionTooLargeException {
- if (r.app == null || r.app.thread == null) {
+ if (r.app == null || r.app.getThread() == null) {
// If service is not currently running, can't yet bind.
return false;
}
@@ -3070,9 +3093,9 @@
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
bumpServiceExecutingLocked(r, execInFg, "bind");
- r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
- r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
- r.app.getReportedProcState());
+ r.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
+ r.app.getThread().scheduleBindService(r, i.intent.getIntent(), rebind,
+ r.app.mState.getReportedProcState());
if (!rebind) {
i.requested = true;
}
@@ -3295,7 +3318,7 @@
boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
boolean enqueueOomAdj)
throws TransactionTooLargeException {
- if (r.app != null && r.app.thread != null) {
+ if (r.app != null && r.app.getThread() != null) {
sendServiceArgsLocked(r, execInFg, false);
return null;
}
@@ -3353,19 +3376,26 @@
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
+ " app=" + app);
- if (app != null && app.thread != null) {
- try {
- app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
- realStartServiceLocked(r, app, execInFg, enqueueOomAdj);
- return null;
- } catch (TransactionTooLargeException e) {
- throw e;
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception when starting service " + r.shortInstanceName, e);
- }
+ if (app != null) {
+ final IApplicationThread thread = app.getThread();
+ final int pid = app.getPid();
+ final UidRecord uidRecord = app.getUidRecord();
+ if (thread != null) {
+ try {
+ app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode,
+ mAm.mProcessStats);
+ realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
+ enqueueOomAdj);
+ return null;
+ } catch (TransactionTooLargeException e) {
+ throw e;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception when starting service " + r.shortInstanceName, e);
+ }
- // If a dead object exception was thrown -- fall through to
- // restart the application.
+ // If a dead object exception was thrown -- fall through to
+ // restart the application.
+ }
}
} else {
// If this service runs in an isolated process, then each time
@@ -3390,8 +3420,8 @@
if (app == null && !permissionsReviewRequired && !packageFrozen) {
// TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service
// was initiated from a notification tap or not.
- if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
- hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) {
+ if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
+ hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
@@ -3447,21 +3477,23 @@
* The "start" here means bring up the instance in the client, and this method is called
* from bindService() as well.
*/
- private final void realStartServiceLocked(ServiceRecord r,
- ProcessRecord app, boolean execInFg, boolean enqueueOomAdj) throws RemoteException {
- if (app.thread == null) {
+ private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
+ IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
+ boolean enqueueOomAdj) throws RemoteException {
+ if (thread == null) {
throw new RemoteException();
}
if (DEBUG_MU)
Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid
+ ", ProcessRecord.uid = " + app.uid);
- r.setProcess(app);
+ r.setProcess(app, thread, pid, uidRecord);
r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
- final boolean newService = app.startService(r);
+ final ProcessServiceRecord psr = app.mServices;
+ final boolean newService = psr.startService(r);
bumpServiceExecutingLocked(r, execInFg, "create");
mAm.updateLruProcessLocked(app, false, null);
- updateServiceForegroundLocked(r.app, /* oomAdj= */ false);
+ updateServiceForegroundLocked(psr, /* oomAdj= */ false);
if (enqueueOomAdj) {
mAm.enqueueOomAdjTargetLocked(app);
} else {
@@ -3476,7 +3508,7 @@
nameTerm = lastPeriod >= 0 ? r.shortInstanceName.substring(lastPeriod)
: r.shortInstanceName;
EventLogTags.writeAmCreateService(
- r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
+ r.userId, System.identityHashCode(r), nameTerm, r.app.uid, pid);
}
final int uid = r.appInfo.uid;
@@ -3487,10 +3519,10 @@
mAm.mBatteryStatsService.noteServiceStartLaunch(uid, packageName, serviceName);
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
- app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
- app.thread.scheduleCreateService(r, r.serviceInfo,
+ app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
+ thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
- app.getReportedProcState());
+ app.mState.getReportedProcState());
r.postNotification();
created = true;
} catch (DeadObjectException e) {
@@ -3505,8 +3537,8 @@
// Cleanup.
if (newService) {
- app.stopService(r);
- r.setProcess(null);
+ psr.stopService(r);
+ r.setProcess(null, null, 0, null);
}
// Retry.
@@ -3517,15 +3549,15 @@
}
if (r.whitelistManager) {
- app.mAllowlistManager = true;
+ psr.mAllowlistManager = true;
}
requestServiceBindingsLocked(r, execInFg);
- updateServiceClientActivitiesLocked(app, null, true);
+ updateServiceClientActivitiesLocked(psr, null, true);
if (newService && created) {
- app.addBoundClientUidsOfNewService(r);
+ psr.addBoundClientUidsOfNewService(r);
}
// If the service is in the started state, and there are no
@@ -3619,7 +3651,7 @@
slice.setInlineCountLimit(4);
Exception caughtException = null;
try {
- r.app.thread.scheduleServiceArgs(r, slice);
+ r.app.getThread().scheduleServiceArgs(r, slice);
} catch (TransactionTooLargeException e) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size()
+ " args, first: " + args.get(0).args);
@@ -3711,7 +3743,7 @@
boolean needOomAdj = false;
// Tell the service that it has been unbound.
- if (r.app != null && r.app.thread != null) {
+ if (r.app != null && r.app.getThread() != null) {
for (int i = r.bindings.size() - 1; i >= 0; i--) {
IntentBindRecord ibr = r.bindings.valueAt(i);
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr
@@ -3722,7 +3754,7 @@
needOomAdj = true;
ibr.hasBound = false;
ibr.requested = false;
- r.app.thread.scheduleUnbindService(r,
+ r.app.getThread().scheduleUnbindService(r,
ibr.intent.getIntent());
} catch (Exception e) {
Slog.w(TAG, "Exception when unbinding service "
@@ -3769,7 +3801,7 @@
r.destroyTime = SystemClock.uptimeMillis();
if (LOG_SERVICE_START_STOP) {
EventLogTags.writeAmDestroyService(
- r.userId, System.identityHashCode(r), (r.app != null) ? r.app.pid : -1);
+ r.userId, System.identityHashCode(r), (r.app != null) ? r.app.getPid() : -1);
}
final ServiceMap smap = getServiceMapLocked(r.userId);
@@ -3831,20 +3863,15 @@
if (r.app != null) {
mAm.mBatteryStatsService.noteServiceStopLaunch(r.appInfo.uid, r.name.getPackageName(),
r.name.getClassName());
- r.app.stopService(r);
- r.app.updateBoundClientUids();
- if (r.whitelistManager) {
- updateAllowlistManagerLocked(r.app);
- }
- if (r.app.thread != null) {
- updateServiceForegroundLocked(r.app, false);
+ stopServiceAndUpdateAllowlistManagerLocked(r);
+ if (r.app.getThread() != null) {
+ updateServiceForegroundLocked(r.app.mServices, false);
try {
bumpServiceExecutingLocked(r, false, "destroy");
mDestroyingServices.add(r);
r.destroying = true;
- mAm.updateOomAdjLocked(r.app, true,
- OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
- r.app.thread.scheduleStopService(r);
+ needOomAdj = true;
+ r.app.getThread().scheduleStopService(r);
} catch (Exception e) {
Slog.w(TAG, "Exception when destroying service "
+ r.shortInstanceName, e);
@@ -3906,16 +3933,17 @@
c.activity.removeConnection(c);
}
if (b.client != skipApp) {
- b.client.connections.remove(c);
+ final ProcessServiceRecord psr = b.client.mServices;
+ psr.removeConnection(c);
if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
- b.client.updateHasAboveClientLocked();
+ psr.updateHasAboveClientLocked();
}
// If this connection requested whitelist management, see if we should
// now clear that state.
if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) {
s.updateWhitelistManager();
if (!s.whitelistManager && s.app != null) {
- updateAllowlistManagerLocked(s.app);
+ updateAllowlistManagerLocked(s.app.mServices);
}
}
// And do the same for bg activity starts ability.
@@ -3926,7 +3954,7 @@
s.updateIsAllowedBgFgsStartsByBinding();
}
if (s.app != null) {
- updateServiceClientActivitiesLocked(s.app, c, true);
+ updateServiceClientActivitiesLocked(s.app.mServices, c, true);
}
}
clist = mServiceConnections.get(binder);
@@ -3947,12 +3975,12 @@
if (!c.serviceDead) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent
+ ": shouldUnbind=" + b.intent.hasBound);
- if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
+ if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
&& b.intent.hasBound) {
try {
bumpServiceExecutingLocked(s, false, "unbind");
if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
- && s.app.setProcState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
// If this service's process is not already in the cached list,
// then update it in the LRU list here because this may be causing
// it to go down there and we want it to start out near the top.
@@ -3968,7 +3996,7 @@
// Assume the client doesn't want to know about a rebind;
// we will deal with that later if it asks for one.
b.intent.doRebind = false;
- s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
+ s.app.getThread().scheduleUnbindService(s, b.intent.intent.getIntent());
} catch (Exception e) {
Slog.w(TAG, "Exception when unbinding service " + s.shortInstanceName, e);
serviceProcessGoneLocked(s, enqueueOomAdj);
@@ -4099,19 +4127,20 @@
r.executeNesting--;
if (r.executeNesting <= 0) {
if (r.app != null) {
+ final ProcessServiceRecord psr = r.app.mServices;
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"Nesting at 0 of " + r.shortInstanceName);
- r.app.execServicesFg = false;
- r.app.executingServices.remove(r);
- if (r.app.executingServices.size() == 0) {
+ psr.setExecServicesFg(false);
+ psr.stopExecutingService(r);
+ if (psr.numberOfExecutingServices() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
"No more executingServices of " + r.shortInstanceName);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
} else if (r.executeFg) {
// Need to re-evaluate whether the app still needs to be in the foreground.
- for (int i=r.app.executingServices.size()-1; i>=0; i--) {
- if (r.app.executingServices.valueAt(i).executeFg) {
- r.app.execServicesFg = true;
+ for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
+ if (psr.getExecutingServiceAt(i).executeFg) {
+ psr.setExecServicesFg(true);
break;
}
}
@@ -4141,13 +4170,9 @@
}
if (finishing) {
if (r.app != null && !r.app.isPersistent()) {
- r.app.stopService(r);
- r.app.updateBoundClientUids();
- if (r.whitelistManager) {
- updateAllowlistManagerLocked(r.app);
- }
+ stopServiceAndUpdateAllowlistManagerLocked(r);
}
- r.setProcess(null);
+ r.setProcess(null, null, 0, null);
}
}
}
@@ -4166,11 +4191,15 @@
continue;
}
+ final IApplicationThread thread = proc.getThread();
+ final int pid = proc.getPid();
+ final UidRecord uidRecord = proc.getUidRecord();
mPendingServices.remove(i);
i--;
proc.addPackage(sr.appInfo.packageName, sr.appInfo.longVersionCode,
mAm.mProcessStats);
- realStartServiceLocked(sr, proc, sr.createdFromFg, true);
+ realStartServiceLocked(sr, proc, thread, pid, uidRecord, sr.createdFromFg,
+ true);
didSomething = true;
if (!isServiceNeededLocked(sr, false, false)) {
// We were waiting for this service to start, but it is actually no
@@ -4245,13 +4274,9 @@
didSomething = true;
Slog.i(TAG, " Force stopping service " + service);
if (service.app != null && !service.app.isPersistent()) {
- service.app.stopService(service);
- service.app.updateBoundClientUids();
- if (service.whitelistManager) {
- updateAllowlistManagerLocked(service.app);
- }
+ stopServiceAndUpdateAllowlistManagerLocked(service);
}
- service.setProcess(null);
+ service.setProcess(null, null, 0, null);
service.isolatedProc = null;
if (mTmpCollectionResults == null) {
mTmpCollectionResults = new ArrayList<>();
@@ -4351,7 +4376,7 @@
} else {
sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
sr.getLastStartId(), baseIntent, null, 0));
- if (sr.app != null && sr.app.thread != null) {
+ if (sr.app != null && sr.app.getThread() != null) {
// We always run in the foreground, since this is called as
// part of the "remove task" UI operation.
try {
@@ -4369,13 +4394,14 @@
}
final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
+ final ProcessServiceRecord psr = app.mServices;
// Report disconnected services.
if (false) {
// XXX we are letting the client link to the service for
// death notifications.
- int numberOfRunningServices = app.numberOfRunningServices();
+ int numberOfRunningServices = psr.numberOfRunningServices();
for (int sIndex = 0; sIndex < numberOfRunningServices; sIndex++) {
- ServiceRecord r = app.getRunningServiceAt(sIndex);
+ ServiceRecord r = psr.getRunningServiceAt(sIndex);
ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
for (int conni = connections.size() - 1; conni >= 0; conni--) {
ArrayList<ConnectionRecord> cl = connections.valueAt(conni);
@@ -4397,25 +4423,25 @@
}
// Clean up any connections this application has to other services.
- for (int i = app.connections.size() - 1; i >= 0; i--) {
- ConnectionRecord r = app.connections.valueAt(i);
+ for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
+ ConnectionRecord r = psr.getConnectionAt(i);
removeConnectionLocked(r, app, null, true);
}
- updateServiceConnectionActivitiesLocked(app);
- app.connections.clear();
+ updateServiceConnectionActivitiesLocked(psr);
+ psr.removeAllConnections();
- app.mAllowlistManager = false;
+ psr.mAllowlistManager = false;
// Clear app state from services.
- for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
- ServiceRecord sr = app.getRunningServiceAt(i);
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ ServiceRecord sr = psr.getRunningServiceAt(i);
mAm.mBatteryStatsService.noteServiceStopLaunch(sr.appInfo.uid, sr.name.getPackageName(),
sr.name.getClassName());
if (sr.app != app && sr.app != null && !sr.app.isPersistent()) {
- sr.app.stopService(sr);
- sr.app.updateBoundClientUids();
+ sr.app.mServices.stopService(sr);
+ sr.app.mServices.updateBoundClientUids();
}
- sr.setProcess(null);
+ sr.setProcess(null, null, 0, null);
sr.isolatedProc = null;
sr.executeNesting = 0;
sr.forceClearTracker();
@@ -4437,7 +4463,7 @@
for (int appi=b.apps.size()-1; appi>=0; appi--) {
final ProcessRecord proc = b.apps.keyAt(appi);
// If the process is already gone, skip it.
- if (proc.killedByAm || proc.thread == null) {
+ if (proc.isKilledByAm() || proc.getThread() == null) {
continue;
}
// Only do this for processes that have an auto-create binding;
@@ -4445,7 +4471,7 @@
// service to restart.
final AppBindRecord abind = b.apps.valueAt(appi);
boolean hasCreate = false;
- for (int conni=abind.connections.size()-1; conni>=0; conni--) {
+ for (int conni = abind.connections.size() - 1; conni >= 0; conni--) {
ConnectionRecord conn = abind.connections.valueAt(conni);
if ((conn.flags&(Context.BIND_AUTO_CREATE|Context.BIND_ALLOW_OOM_MANAGEMENT
|Context.BIND_WAIVE_PRIORITY)) == Context.BIND_AUTO_CREATE) {
@@ -4457,13 +4483,15 @@
continue;
}
// XXX turned off for now until we have more time to get a better policy.
- if (false && proc != null && !proc.isPersistent() && proc.thread != null
- && proc.pid != 0 && proc.pid != ActivityManagerService.MY_PID
- && proc.setProcState >= ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
- proc.kill("bound to service " + sr.shortInstanceName
+ /*
+ if (false && proc != null && !proc.isPersistent() && proc.getThread() != null
+ && proc.getPid() != 0 && proc.getPid() != ActivityManagerService.MY_PID
+ && proc.mState.getSetProcState() >= PROCESS_STATE_LAST_ACTIVITY) {
+ proc.killLocked("bound to service " + sr.shortInstanceName
+ " in dying proc " + (app != null ? app.processName : "??"),
ApplicationExitInfo.REASON_OTHER, true);
}
+ */
}
}
}
@@ -4471,14 +4499,14 @@
ServiceMap smap = getServiceMapLocked(app.userId);
// Now do remaining service cleanup.
- for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
- ServiceRecord sr = app.getRunningServiceAt(i);
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ ServiceRecord sr = psr.getRunningServiceAt(i);
// Unless the process is persistent, this process record is going away,
// so make sure the service is cleaned out of it.
if (!app.isPersistent()) {
- app.stopService(sr);
- app.updateBoundClientUids();
+ psr.stopService(sr);
+ psr.updateBoundClientUids();
}
// Sanity check: if the service listed for the app is not one
@@ -4500,7 +4528,7 @@
Slog.w(TAG, "Service crashed " + sr.crashCount
+ " times, stopping: " + sr);
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
- sr.userId, sr.crashCount, sr.shortInstanceName, app.pid);
+ sr.userId, sr.crashCount, sr.shortInstanceName, sr.app.getPid());
bringDownServiceLocked(sr, true);
} else if (!allowRestart
|| !mAm.mUserController.isUserRunning(sr.userId, 0)) {
@@ -4529,8 +4557,8 @@
mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
if (!allowRestart) {
- app.stopAllServices();
- app.clearBoundClientUids();
+ psr.stopAllServices();
+ psr.clearBoundClientUids();
// Make sure there are no more restarting services for this process.
for (int i=mRestartingServices.size()-1; i>=0; i--) {
@@ -4569,7 +4597,7 @@
}
}
- app.executingServices.clear();
+ psr.stopAllExecutingServices();
}
ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) {
@@ -4577,7 +4605,7 @@
new ActivityManager.RunningServiceInfo();
info.service = r.name;
if (r.app != null) {
- info.pid = r.app.pid;
+ info.pid = r.app.getPid();
}
info.uid = r.appInfo.uid;
info.process = r.processName;
@@ -4593,7 +4621,7 @@
if (r.startRequested) {
info.flags |= ActivityManager.RunningServiceInfo.FLAG_STARTED;
}
- if (r.app != null && r.app.pid == ActivityManagerService.MY_PID) {
+ if (r.app != null && r.app.getPid() == ActivityManagerService.MY_PID) {
info.flags |= ActivityManager.RunningServiceInfo.FLAG_SYSTEM_PROCESS;
}
if (r.app != null && r.app.isPersistent()) {
@@ -4692,16 +4720,17 @@
// The app's being debugged, ignore timeout.
return;
}
- if (proc.executingServices.size() == 0 || proc.thread == null) {
+ final ProcessServiceRecord psr = proc.mServices;
+ if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null) {
return;
}
final long now = SystemClock.uptimeMillis();
final long maxTime = now -
- (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+ (psr.shouldExecServicesFg() ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
ServiceRecord timeout = null;
long nextTime = 0;
- for (int i=proc.executingServices.size()-1; i>=0; i--) {
- ServiceRecord sr = proc.executingServices.valueAt(i);
+ for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
+ ServiceRecord sr = psr.getExecutingServiceAt(i);
if (sr.executingStart < maxTime) {
timeout = sr;
break;
@@ -4710,7 +4739,7 @@
nextTime = sr.executingStart;
}
}
- if (timeout != null && mAm.mProcessList.mLruProcesses.contains(proc)) {
+ if (timeout != null && mAm.mProcessList.isInLruListLOSP(proc)) {
Slog.w(TAG, "Timeout executing service: " + timeout);
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 1024);
@@ -4725,7 +4754,7 @@
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
- mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
+ mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
}
}
@@ -4779,24 +4808,24 @@
}
void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) {
- mAm.crashApplication(app.uid, app.pid, app.info.packageName, app.userId,
+ mAm.crashApplication(app.uid, app.getPid(), app.info.packageName, app.userId,
"Context.startForegroundService() did not then call Service.startForeground(): "
+ serviceRecord, false /*force*/);
}
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
- if (proc.executingServices.size() == 0 || proc.thread == null) {
+ if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
- mAm.mHandler.sendMessageDelayed(msg,
- proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+ mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
+ ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
- if (r.app.executingServices.size() == 0 || r.app.thread == null) {
+ if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
@@ -4993,7 +5022,7 @@
if (proc == null) {
return;
}
- final IApplicationThread thread = proc.thread;
+ final IApplicationThread thread = proc.getThread();
if (thread == null) {
return;
}
@@ -5319,20 +5348,21 @@
pw.print(Integer.toHexString(System.identityHashCode(r)));
pw.print(" pid=");
if (r.app != null) {
- pw.print(r.app.pid);
+ pw.print(r.app.getPid());
pw.print(" user="); pw.println(r.userId);
} else pw.println("(not running)");
if (dumpAll) {
r.dump(pw, innerPrefix);
}
}
- if (r.app != null && r.app.thread != null) {
+ IApplicationThread thread;
+ if (r.app != null && (thread = r.app.getThread()) != null) {
pw.print(prefix); pw.println(" Client:");
pw.flush();
try {
TransferPipe tp = new TransferPipe();
try {
- r.app.thread.dumpService(tp.getWriteFd(), r, args);
+ thread.dumpService(tp.getWriteFd(), r, args);
tp.setBufferPrefix(prefix + " ");
tp.go(fd);
} finally {
@@ -5394,10 +5424,10 @@
boolean allowBackgroundActivityStarts) {
int ret = FGS_FEATURE_DENIED;
- final int uidState = mAm.getUidState(callingUid);
+ final int uidState = mAm.getUidStateLocked(callingUid);
if (ret == FGS_FEATURE_DENIED) {
// Is the calling UID at PROCESS_STATE_TOP or above?
- if (uidState <= ActivityManager.PROCESS_STATE_TOP) {
+ if (uidState <= PROCESS_STATE_TOP) {
ret = FGS_FEATURE_ALLOWED_BY_UID_STATE;
}
}
@@ -5438,14 +5468,16 @@
}
if (ret == FGS_FEATURE_DENIED) {
- for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
- final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
+ final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {
if (pr.uid == callingUid) {
if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {
- ret = FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER;
- break;
+ return FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER;
}
}
+ return null;
+ });
+ if (allowedType != null) {
+ ret = allowedType;
}
}
@@ -5500,46 +5532,37 @@
int ret = allowWhileInUse;
final StringBuilder sb = new StringBuilder(64);
- final int uidState = mAm.getUidState(callingUid);
+ final int uidState = mAm.getUidStateLocked(callingUid);
if (ret == FGS_FEATURE_DENIED) {
// Is the calling UID at PROCESS_STATE_TOP or above?
- if (uidState <= ActivityManager.PROCESS_STATE_TOP) {
+ if (uidState <= PROCESS_STATE_TOP) {
sb.append("uidState=").append(uidState);
ret = FGS_FEATURE_ALLOWED_BY_UID_STATE;
}
}
if (ret == FGS_FEATURE_DENIED) {
- for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
- final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
- if (pr.uid == callingUid) {
- if (pr.mAllowStartFgs) {
- ret = FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD;
- break;
- } else if (pr.isAllowedStartFgsState()) {
- ret = FGS_FEATURE_ALLOWED_BY_PROC_STATE;
- break;
- }
- }
- }
- }
-
- if (ret == FGS_FEATURE_DENIED) {
- for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
- final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
- if (pr.uid == callingUid) {
- if (pr.areBackgroundFgsStartsAllowedByToken()) {
- ret = FGS_FEATURE_ALLOWED_BY_FGS_BINDING;
- break;
+ final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
+ if (app.uid == callingUid) {
+ final ProcessStateRecord state = app.mState;
+ if (state.getAllowedStartFgs() != FGS_FEATURE_DENIED) {
+ return state.getAllowedStartFgs();
+ } else if (state.isAllowedStartFgsState()) {
+ return FGS_FEATURE_ALLOWED_BY_PROC_STATE;
+ } else if (state.areBackgroundFgsStartsAllowedByToken()) {
+ return FGS_FEATURE_ALLOWED_BY_FGS_BINDING;
} else {
- final ActiveInstrumentation instr = pr.getActiveInstrumentation();
+ final ActiveInstrumentation instr = app.getActiveInstrumentation();
if (instr != null
&& instr.mHasBackgroundForegroundServiceStartsPermission) {
- ret = FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION;
- break;
+ return FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION;
}
}
}
+ return null;
+ });
+ if (allowedType != null) {
+ ret = allowedType;
}
}
@@ -5558,7 +5581,7 @@
}
if (ret == FGS_FEATURE_DENIED) {
- if (mAm.isAllowlistedForFgsStartLocked(callingUid)) {
+ if (mAm.isAllowlistedForFgsStartLOSP(callingUid)) {
// uid is on DeviceIdleController's user/system allowlist
// or AMS's FgsStartTempAllowList.
ret = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST;
@@ -5634,7 +5657,7 @@
return CompatChanges.isChangeEnabled(FGS_BG_START_USE_EXEMPTION_LIST_CHANGE_ID, uid);
}
- private static String fgsCodeToString(@FgsFeatureRetCode int code) {
+ static String fgsCodeToString(@FgsFeatureRetCode int code) {
switch (code) {
case FGS_FEATURE_DENIED:
return "DENIED";
@@ -5720,4 +5743,23 @@
return mAm.mConstants.mFlagFgsStartRestrictionEnabled
&& CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid);
}
+
+ private void logFgsBackgroundStart(ServiceRecord r) {
+ // Only log if FGS is started from background.
+ if (!isFgsBgStart(r.mAllowStartForeground)) {
+ return;
+ }
+ if (!r.mLoggedInfoAllowStartForeground) {
+ final String msg = "Background started FGS: "
+ + ((r.mAllowStartForeground != FGS_FEATURE_DENIED) ? "Allowed " : "Disallowed ")
+ + r.mInfoAllowStartForeground;
+ Slog.wtfQuiet(TAG, msg);
+ if (r.mAllowStartForeground != FGS_FEATURE_DENIED) {
+ Slog.i(TAG, msg);
+ } else {
+ Slog.w(TAG, msg);
+ }
+ r.mLoggedInfoAllowStartForeground = true;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActiveUids.java b/services/core/java/com/android/server/am/ActiveUids.java
index 27be53a..31db95b7 100644
--- a/services/core/java/com/android/server/am/ActiveUids.java
+++ b/services/core/java/com/android/server/am/ActiveUids.java
@@ -21,21 +21,29 @@
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
/** Class for tracking active uids for running processes. */
final class ActiveUids {
- private ActivityManagerService mService;
+ private final ActivityManagerService mService;
+ private final ActivityManagerGlobalLock mProcLock;
- private boolean mPostChangesToAtm;
+ private final boolean mPostChangesToAtm;
+
+ @CompositeRWLock({"mService", "mProcLock"})
private final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
ActiveUids(ActivityManagerService service, boolean postChangesToAtm) {
mService = service;
+ mProcLock = service != null ? service.mProcLock : null;
mPostChangesToAtm = postChangesToAtm;
}
+ @GuardedBy({"mService", "mProcLock"})
void put(int uid, UidRecord value) {
mActiveUids.put(uid, value);
if (mPostChangesToAtm) {
@@ -43,6 +51,7 @@
}
}
+ @GuardedBy({"mService", "mProcLock"})
void remove(int uid) {
mActiveUids.remove(uid);
if (mPostChangesToAtm) {
@@ -50,38 +59,45 @@
}
}
+ @GuardedBy({"mService", "mProcLock"})
void clear() {
mActiveUids.clear();
// It is only called for a temporal container with mPostChangesToAtm == false or test case.
// So there is no need to notify activity task manager.
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
UidRecord get(int uid) {
return mActiveUids.get(uid);
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
int size() {
return mActiveUids.size();
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
UidRecord valueAt(int index) {
return mActiveUids.valueAt(index);
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
int keyAt(int index) {
return mActiveUids.keyAt(index);
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
int indexOfKey(int uid) {
return mActiveUids.indexOfKey(uid);
}
- boolean dump(PrintWriter pw, String dumpPackage, int dumpAppId,
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean dump(final PrintWriter pw, String dumpPackage, int dumpAppId,
String header, boolean needSep) {
boolean printed = false;
for (int i = 0; i < mActiveUids.size(); i++) {
final UidRecord uidRec = mActiveUids.valueAt(i);
- if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.getUid()) != dumpAppId) {
continue;
}
if (!printed) {
@@ -91,16 +107,16 @@
}
pw.print(" "); pw.println(header);
}
- pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
+ pw.print(" UID "); UserHandle.formatUid(pw, uidRec.getUid());
pw.print(": "); pw.println(uidRec);
- pw.print(" curProcState="); pw.print(uidRec.mCurProcState);
+ pw.print(" curProcState="); pw.print(uidRec.getCurProcState());
pw.print(" curCapability=");
- ActivityManager.printCapabilitiesFull(pw, uidRec.curCapability);
+ ActivityManager.printCapabilitiesFull(pw, uidRec.getCurCapability());
pw.println();
- for (int j = uidRec.procRecords.size() - 1; j >= 0; j--) {
+ uidRec.forEachProcess(app -> {
pw.print(" proc=");
- pw.println(uidRec.procRecords.valueAt(j));
- }
+ pw.println(app);
+ });
}
return printed;
}
@@ -108,7 +124,7 @@
void dumpProto(ProtoOutputStream proto, String dumpPackage, int dumpAppId, long fieldId) {
for (int i = 0; i < mActiveUids.size(); i++) {
UidRecord uidRec = mActiveUids.valueAt(i);
- if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.getUid()) != dumpAppId) {
continue;
}
uidRec.dumpDebug(proto, fieldId);
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/services/core/java/com/android/server/am/ActivityManagerGlobalLock.java
similarity index 72%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to services/core/java/com/android/server/am/ActivityManagerGlobalLock.java
index 19b20f2..7823038 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/services/core/java/com/android/server/am/ActivityManagerGlobalLock.java
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+package com.android.server.am;
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+/**
+ * Interface to mark whichever class with the implementation is considered as a global lock
+ * to be used in the package of ActivityManagerService.
+ */
+interface ActivityManagerGlobalLock {
+}
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/services/core/java/com/android/server/am/ActivityManagerProcLock.java
similarity index 63%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to services/core/java/com/android/server/am/ActivityManagerProcLock.java
index 19b20f2..3ef19ad 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/services/core/java/com/android/server/am/ActivityManagerProcLock.java
@@ -14,7 +14,15 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+package com.android.server.am;
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+/**
+ * Class that is used to generate an instance of the {@link ActivityManagerService#mProcLock},
+ * so the CPU booster can identify the critical section.
+ *
+ * <p>
+ * Use "-Lpr" as the suffix of functions locked with this lock.
+ * </p>
+ */
+final class ActivityManagerProcLock implements ActivityManagerGlobalLock {
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c8f5f8e..75d8cd2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -27,6 +27,7 @@
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
@@ -110,7 +111,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -249,6 +249,7 @@
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
+import android.os.PowerWhitelistManager.TempAllowListType;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
@@ -294,6 +295,7 @@
import android.view.WindowManager;
import android.view.autofill.AutofillManagerInternal;
+import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
@@ -396,7 +398,7 @@
import java.util.concurrent.atomic.AtomicInteger;
public class ActivityManagerService extends IActivityManager.Stub
- implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
+ implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
/**
* Priority we boost main thread and RT of top app to.
@@ -416,7 +418,6 @@
static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
private static final String TAG_POWER = TAG + POSTFIX_POWER;
- static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES;
private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -527,12 +528,55 @@
final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
+ @CompositeRWLock({"this", "mProcLock"})
final ArrayList<ActiveInstrumentation> mActiveInstrumentation = new ArrayList<>();
public final IntentFirewall mIntentFirewall;
public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
+ /**
+ * The global lock for AMS, it's de-facto the ActivityManagerService object as of now.
+ */
+ final ActivityManagerGlobalLock mGlobalLock = ActivityManagerService.this;
+
+ /**
+ * Whether or not to enable the {@link #mProcLock}. If {@code false}, the {@link #mProcLock}
+ * will be equivalent to the {@link #mGlobalLock}.
+ */
+ private static final boolean ENABLE_PROC_LOCK = true;
+
+ /**
+ * The lock for process management.
+ *
+ * <p>
+ * This lock is widely used in conjunction with the {@link #mGlobalLock} at present,
+ * where it'll require any of the locks to read from a data class, and both of the locks
+ * to write into that data class.
+ *
+ * For the naming convention of function suffixes:
+ * <ul>
+ * <li>-LOSP: Locked with any Of global am Service or Process lock</li>
+ * <li>-LSP: Locked with both of global am Service and Process lock</li>
+ * <li>-Locked: Locked with global am service lock alone</li>
+ * <li>-LPr: Locked with Process lock alone</li>
+ * </ul>
+ * For the simplicity, the getters/setters of the fields in data classes usually don't end with
+ * the above suffixes even if they're guarded by the locks here.
+ * </p>
+ *
+ * <p>
+ * In terms of locking order, it should be right below to the {@link #mGlobalLock},
+ * and above everything else which used to be underneath the {@link #mGlobalLock}.
+ * As of today, the core components(services/providers/broadcasts) are still guarded by
+ * the {@link #mGlobalLock} alone, so be cautious, avoid from acquiring the {@link #mGlobalLock}
+ * while holding this lock.
+ * </p>
+ *
+ */
+ final ActivityManagerGlobalLock mProcLock = ENABLE_PROC_LOCK
+ ? new ActivityManagerProcLock() : mGlobalLock;
+
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
@@ -548,7 +592,10 @@
// so that dispatch of foreground broadcasts gets precedence.
final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[3];
+ @GuardedBy("this")
BroadcastStats mLastBroadcastStats;
+
+ @GuardedBy("this")
BroadcastStats mCurBroadcastStats;
BroadcastQueue broadcastQueueForIntent(Intent intent) {
@@ -569,10 +616,11 @@
/**
* The package name of the DeviceOwner. This package is not permitted to have its data cleared.
+ * <p>Not actually used</p>
*/
- String mDeviceOwnerName;
+ private volatile String mDeviceOwnerName;
- private int mDeviceOwnerUid = Process.INVALID_UID;
+ private volatile int mDeviceOwnerUid = Process.INVALID_UID;
/**
* Map userId to its companion app uids.
@@ -643,6 +691,25 @@
sThreadPriorityBooster.reset();
}
+ private static ThreadPriorityBooster sProcThreadPriorityBooster = new ThreadPriorityBooster(
+ THREAD_PRIORITY_FOREGROUND, LockGuard.INDEX_PROC);
+
+ static void boostPriorityForProcLockedSection() {
+ if (ENABLE_PROC_LOCK) {
+ sProcThreadPriorityBooster.boost();
+ } else {
+ sThreadPriorityBooster.boost();
+ }
+ }
+
+ static void resetPriorityAfterProcLockedSection() {
+ if (ENABLE_PROC_LOCK) {
+ sProcThreadPriorityBooster.reset();
+ } else {
+ sThreadPriorityBooster.reset();
+ }
+ }
+
/**
* Process management.
*/
@@ -663,20 +730,23 @@
/**
* Non-persistent appId allowlist for background restrictions
*/
- int[] mBackgroundAppIdAllowlist = new int[] {
+ @CompositeRWLock({"this", "mProcLock"})
+ private int[] mBackgroundAppIdAllowlist = new int[] {
BLUETOOTH_UID
};
/**
* Broadcast actions that will always be deliverable to unlaunched/background apps
*/
- ArraySet<String> mBackgroundLaunchBroadcasts;
+ @GuardedBy("this")
+ private ArraySet<String> mBackgroundLaunchBroadcasts;
/**
* When an app has restrictions on the other apps that can have associations with it,
* it appears here with a set of the allowed apps and also track debuggability of the app.
*/
- ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
+ @GuardedBy("this")
+ private ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
/**
* Tracks association information for a particular package along with debuggability.
@@ -754,24 +824,24 @@
return mPidMap.indexOfKey(key);
}
- void doAddInternal(ProcessRecord app) {
- mPidMap.put(app.pid, app);
+ void doAddInternal(int pid, ProcessRecord app) {
+ mPidMap.put(pid, app);
}
- boolean doRemoveInternal(ProcessRecord app) {
- final ProcessRecord existingApp = mPidMap.get(app.pid);
- if (existingApp != null && existingApp.startSeq == app.startSeq) {
- mPidMap.remove(app.pid);
+ boolean doRemoveInternal(int pid, ProcessRecord app) {
+ final ProcessRecord existingApp = mPidMap.get(pid);
+ if (existingApp != null && existingApp.getStartSeq() == app.getStartSeq()) {
+ mPidMap.remove(pid);
return true;
}
return false;
}
- boolean doRemoveIfNoThreadInternal(ProcessRecord app) {
- if (app == null || app.thread != null) {
+ boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
+ if (app == null || app.getThread() != null) {
return false;
}
- return doRemoveInternal(app);
+ return doRemoveInternal(pid, app);
}
}
@@ -782,18 +852,20 @@
* <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
* method.
*/
+ @GuardedBy("this")
void addPidLocked(ProcessRecord app) {
+ final int pid = app.getPid();
synchronized (mPidsSelfLocked) {
- mPidsSelfLocked.doAddInternal(app);
+ mPidsSelfLocked.doAddInternal(pid, app);
}
synchronized (sActiveProcessInfoSelfLocked) {
if (app.processInfo != null) {
- sActiveProcessInfoSelfLocked.put(app.pid, app.processInfo);
+ sActiveProcessInfoSelfLocked.put(pid, app.processInfo);
} else {
- sActiveProcessInfoSelfLocked.remove(app.pid);
+ sActiveProcessInfoSelfLocked.remove(pid);
}
}
- mAtmInternal.onProcessMapped(app.pid, app.getWindowProcessController());
+ mAtmInternal.onProcessMapped(pid, app.getWindowProcessController());
}
/**
@@ -801,16 +873,17 @@
* <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
* method.
*/
- void removePidLocked(ProcessRecord app) {
+ @GuardedBy("this")
+ void removePidLocked(int pid, ProcessRecord app) {
final boolean removed;
synchronized (mPidsSelfLocked) {
- removed = mPidsSelfLocked.doRemoveInternal(app);
+ removed = mPidsSelfLocked.doRemoveInternal(pid, app);
}
if (removed) {
synchronized (sActiveProcessInfoSelfLocked) {
- sActiveProcessInfoSelfLocked.remove(app.pid);
+ sActiveProcessInfoSelfLocked.remove(pid);
}
- mAtmInternal.onProcessUnMapped(app.pid);
+ mAtmInternal.onProcessUnMapped(pid);
}
}
@@ -819,16 +892,18 @@
* <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
* method.
*/
- boolean removePidIfNoThread(ProcessRecord app) {
+ @GuardedBy("this")
+ private boolean removePidIfNoThreadLocked(ProcessRecord app) {
final boolean removed;
+ final int pid = app.getPid();
synchronized (mPidsSelfLocked) {
- removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(app);
+ removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
}
if (removed) {
synchronized (sActiveProcessInfoSelfLocked) {
- sActiveProcessInfoSelfLocked.remove(app.pid);
+ sActiveProcessInfoSelfLocked.remove(pid);
}
- mAtmInternal.onProcessUnMapped(app.pid);
+ mAtmInternal.onProcessUnMapped(pid);
}
return removed;
}
@@ -865,6 +940,7 @@
proto.end(pToken);
}
}
+ @GuardedBy("this")
final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>();
/**
@@ -872,12 +948,14 @@
* system was ready. We don't start them at that point, but ensure they
* are started by the time booting is complete.
*/
+ @GuardedBy("this")
final ArrayList<ProcessRecord> mProcessesOnHold = new ArrayList<ProcessRecord>();
/**
* List of persistent applications that are in the process
* of being started.
*/
+ @GuardedBy("this")
final ArrayList<ProcessRecord> mPersistentStartingProcesses = new ArrayList<ProcessRecord>();
private final ActivityMetricsLaunchObserver mActivityLaunchObserver =
@@ -925,6 +1003,7 @@
* Keeps track of all IIntentReceivers that have been registered for broadcasts.
* Hash keys are the receiver IBinder, hash value is a ReceiverList.
*/
+ @GuardedBy("this")
final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
/**
@@ -977,6 +1056,7 @@
* by the user ID the sticky is for, and can include UserHandle.USER_ALL
* for stickies that are sent to all users.
*/
+ @GuardedBy("this")
final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =
new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
@@ -1016,6 +1096,7 @@
* have seen. Mapping is target uid -> target component -> source uid -> source process name
* -> association data.
*/
+ @GuardedBy("this")
final SparseArray<ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>>>
mAssociations = new SparseArray<>();
boolean mTrackingAssociations;
@@ -1067,16 +1148,19 @@
/**
* Power-save whitelisted app-ids (not including except-idle-whitelisted ones).
*/
+ @CompositeRWLock({"this", "mProcLock"})
int[] mDeviceIdleAllowlist = new int[0];
/**
* Power-save whitelisted app-ids (including except-idle-whitelisted ones).
*/
+ @CompositeRWLock({"this", "mProcLock"})
int[] mDeviceIdleExceptIdleAllowlist = new int[0];
/**
* Set of app ids that are temporarily allowed to escape bg check due to high-pri message
*/
+ @CompositeRWLock({"this", "mProcLock"})
int[] mDeviceIdleTempAllowlist = new int[0];
static final class PendingTempAllowlist {
@@ -1102,11 +1186,13 @@
}
}
+ @CompositeRWLock({"this", "mProcLock"})
final PendingTempAllowlists mPendingTempAllowlist = new PendingTempAllowlists(this);
/**
* The temp-allowlist that is allowed to start FGS from background.
*/
+ @CompositeRWLock({"this", "mProcLock"})
final FgsStartTempAllowList mFgsStartTempAllowList = new FgsStartTempAllowList();
/**
@@ -1122,11 +1208,6 @@
ArrayMap<String, IBinder> mAppBindArgs;
ArrayMap<String, IBinder> mIsolatedAppBindArgs;
- /**
- * Temporary to avoid allocations. Protected by main lock.
- */
- final StringBuilder mStringBuilder = new StringBuilder(256);
-
volatile boolean mProcessesReady = false;
volatile boolean mSystemReady = false;
volatile boolean mOnBattery = false;
@@ -1147,6 +1228,7 @@
/**
* Last time (in uptime) at which we checked for power usage.
*/
+ @GuardedBy("mProcLock")
long mLastPowerCheckUptime;
/**
@@ -1162,10 +1244,14 @@
/**
* The uptime of the last time we performed idle maintenance.
*/
+ @GuardedBy("mProcLock")
long mLastIdleTime = SystemClock.uptimeMillis();
/**
* For reporting to battery stats the current top application.
+ *
+ * <p>It has its own lock to avoid from the need of double locking if using the global
+ * ActivityManagerService lock and proc lock to guard it.</p>
*/
@GuardedBy("mCurResumedAppLock")
private String mCurResumedPackage = null;
@@ -1183,22 +1269,38 @@
* service. The ProcessMap is package/uid tuples; each of these contain
* an array of the currently foreground processes.
*/
+ @GuardedBy("this")
final ProcessMap<ArrayList<ProcessRecord>> mForegroundPackages
= new ProcessMap<ArrayList<ProcessRecord>>();
/**
* Set if the systemServer made a call to enterSafeMode.
*/
+ @GuardedBy("this")
boolean mSafeMode;
- String mDebugApp = null;
- boolean mWaitForDebugger = false;
- boolean mDebugTransient = false;
- String mOrigDebugApp = null;
- boolean mOrigWaitForDebugger = false;
+ @GuardedBy("this")
+ private String mDebugApp = null;
+
+ @GuardedBy("this")
+ private boolean mWaitForDebugger = false;
+
+ @GuardedBy("this")
+ private boolean mDebugTransient = false;
+
+ @GuardedBy("this")
+ private String mOrigDebugApp = null;
+
+ @GuardedBy("this")
+ private boolean mOrigWaitForDebugger = false;
+
+ @GuardedBy("this")
boolean mAlwaysFinishActivities = false;
- String mTrackAllocationApp = null;
+ @GuardedBy("mProcLock")
+ private String mTrackAllocationApp = null;
+
+ @GuardedBy("this")
String mNativeDebuggingApp = null;
final Injector mInjector;
@@ -1343,10 +1445,12 @@
/**
* Whether to force background check on all apps (for battery saver) or not.
*/
- boolean mForceBackgroundCheck;
+ @CompositeRWLock({"this", "mProcLock"})
+ private boolean mForceBackgroundCheck;
private static String sTheRealBuildSerial = Build.UNKNOWN;
+ @GuardedBy("mProcLock")
private ParcelFileDescriptor[] mLifeMonitorFds;
static final HostingRecord sNullHostingRecord = new HostingRecord(null);
@@ -1368,6 +1472,7 @@
/**
* The last time when the binder heavy hitter auto sampler started.
*/
+ @GuardedBy("mProcLock")
private long mLastBinderHeavyHitterAutoSamplerStart = 0L;
final AppProfiler mAppProfiler;
@@ -1405,19 +1510,19 @@
} break;
case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
ProcessRecord proc = (ProcessRecord) data.get("app");
if (proc == null) {
Slog.e(TAG, "App not found when showing strict mode dialog.");
break;
}
- if (proc.getDialogController().hasViolationDialogs()) {
+ if (proc.mErrorState.getDialogController().hasViolationDialogs()) {
Slog.e(TAG, "App already has strict mode dialog: " + proc);
return;
}
AppErrorResult res = (AppErrorResult) data.get("result");
if (mAtmInternal.showStrictModeViolationDialog()) {
- proc.getDialogController().showViolationDialogs(res);
+ proc.mErrorState.getDialogController().showViolationDialogs(res);
} else {
// The device is asleep, so just pretend that the user
// saw a crash dialog and hit "force quit".
@@ -1427,15 +1532,15 @@
ensureBootCompleted();
} break;
case WAIT_FOR_DEBUGGER_UI_MSG: {
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
ProcessRecord app = (ProcessRecord) msg.obj;
if (msg.arg1 != 0) {
- if (!app.waitedForDebugger) {
- app.getDialogController().showDebugWaitingDialogs();
- app.waitedForDebugger = true;
+ if (!app.hasWaitedForDebugger()) {
+ app.mErrorState.getDialogController().showDebugWaitingDialogs();
+ app.setWaitedForDebugger(true);
}
} else {
- app.getDialogController().clearWaitingDialog();
+ app.mErrorState.getDialogController().clearWaitingDialog();
}
}
} break;
@@ -1486,23 +1591,23 @@
msg.getData().getCharSequence(SERVICE_RECORD_KEY));
} break;
case UPDATE_TIME_ZONE: {
- synchronized (ActivityManagerService.this) {
- for (int i = mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
- ProcessRecord r = mProcessList.mLruProcesses.get(i);
- if (r.thread != null) {
+ synchronized (mProcLock) {
+ mProcessList.forEachLruProcessesLOSP(false, app -> {
+ final IApplicationThread thread = app.getThread();
+ if (thread != null) {
try {
- r.thread.updateTimeZone();
+ thread.updateTimeZone();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update time zone for: "
- + r.info.processName);
+ + app.info.processName);
}
- }
+ }
+ });
}
- }
} break;
case CLEAR_DNS_CACHE_MSG: {
- synchronized (ActivityManagerService.this) {
- mProcessList.clearAllDnsCacheLocked();
+ synchronized (mProcLock) {
+ mProcessList.clearAllDnsCacheLOSP();
}
} break;
case UPDATE_HTTP_PROXY_MSG: {
@@ -1557,8 +1662,8 @@
case UPDATE_TIME_PREFERENCE_MSG: {
// The user's time format preference might have changed.
// For convenience we re-use the Intent extra values.
- synchronized (ActivityManagerService.this) {
- mProcessList.updateAllTimePrefsLocked(msg.arg1);
+ synchronized (mProcLock) {
+ mProcessList.updateAllTimePrefsLOSP(msg.arg1);
}
break;
}
@@ -1566,19 +1671,21 @@
final int uid = msg.arg1;
final byte[] firstPacket = (byte[]) msg.obj;
- synchronized (mPidsSelfLocked) {
- for (int i = 0; i < mPidsSelfLocked.size(); i++) {
- final ProcessRecord p = mPidsSelfLocked.valueAt(i);
- if (p.uid == uid && p.thread != null) {
- try {
- p.thread.notifyCleartextNetwork(firstPacket);
- } catch (RemoteException ignored) {
+ synchronized (mProcLock) {
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+ final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+ final IApplicationThread thread = p.getThread();
+ if (p.uid == uid && thread != null) {
+ try {
+ thread.notifyCleartextNetwork(firstPacket);
+ } catch (RemoteException ignored) {
+ }
}
}
}
}
- break;
- }
+ } break;
case POST_DUMP_HEAP_NOTIFICATION_MSG: {
mAppProfiler.handlePostDumpHeapNotification();
} break;
@@ -1600,8 +1707,8 @@
idleUids();
} break;
case HANDLE_TRUST_STORAGE_UPDATE_MSG: {
- synchronized (ActivityManagerService.this) {
- mProcessList.handleAllTrustStorageUpdateLocked();
+ synchronized (mProcLock) {
+ mProcessList.handleAllTrustStorageUpdateLOSP();
}
} break;
case BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG: {
@@ -1641,12 +1748,12 @@
0,
new HostingRecord("system"));
app.setPersistent(true);
- app.pid = MY_PID;
+ app.mPid = MY_PID;
app.getWindowProcessController().setPid(MY_PID);
- app.maxAdj = ProcessList.SYSTEM_ADJ;
+ app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
addPidLocked(app);
- mProcessList.updateLruProcessLocked(app, false, null);
+ updateLruProcessLocked(app, false, null);
updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
}
} catch (PackageManager.NameNotFoundException e) {
@@ -2296,16 +2403,18 @@
if (code == SYSPROPS_TRANSACTION) {
// We need to tell all apps about the system property change.
ArrayList<IBinder> procs = new ArrayList<IBinder>();
- synchronized (this) {
- final int NP = mProcessList.mProcessNames.getMap().size();
- for (int ip = 0; ip < NP; ip++) {
- SparseArray<ProcessRecord> apps =
- mProcessList.mProcessNames.getMap().valueAt(ip);
- final int NA = apps.size();
- for (int ia = 0; ia < NA; ia++) {
+ synchronized (mProcLock) {
+ final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
+ mProcessList.getProcessNamesLOSP().getMap();
+ final int numOfNames = pmap.size();
+ for (int ip = 0; ip < numOfNames; ip++) {
+ SparseArray<ProcessRecord> apps = pmap.valueAt(ip);
+ final int numOfApps = apps.size();
+ for (int ia = 0; ia < numOfApps; ia++) {
ProcessRecord app = apps.valueAt(ia);
- if (app.thread != null) {
- procs.add(app.thread.asBinder());
+ final IApplicationThread thread = app.getThread();
+ if (thread != null) {
+ procs.add(thread.asBinder());
}
}
}
@@ -2357,10 +2466,8 @@
// When plugging in, update the CPU stats first before changing
// the plug state.
updateCpuStatsNow();
- synchronized (this) {
- synchronized(mPidsSelfLocked) {
- mOnBattery = DEBUG_POWER ? true : onBattery;
- }
+ synchronized (mProcLock) {
+ mOnBattery = DEBUG_POWER ? true : onBattery;
mOomAdjProfiler.batteryPowerChanged(onBattery);
}
}
@@ -2455,21 +2562,25 @@
mActivityTaskManager.unregisterTaskStackListener(listener);
}
+ @GuardedBy("this")
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
mProcessList.updateLruProcessLocked(app, activityChange, client);
}
+ @GuardedBy("this")
final void removeLruProcessLocked(ProcessRecord app) {
mProcessList.removeLruProcessLocked(app);
}
+ @GuardedBy("this")
final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) {
return mProcessList.getProcessRecordLocked(processName, uid, keepIfLarge);
}
- final ProcessMap<ProcessRecord> getProcessNames() {
- return mProcessList.mProcessNames;
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ final ProcessMap<ProcessRecord> getProcessNamesLOSP() {
+ return mProcessList.getProcessNamesLOSP();
}
void notifyPackageUse(String packageName, int reason) {
@@ -2552,7 +2663,7 @@
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(), taskRoot);
}
- final ContentCaptureManagerInternal contentCaptureService = mContentCaptureService;
+ ContentCaptureManagerInternal contentCaptureService = mContentCaptureService;
if (contentCaptureService != null && (event == Event.ACTIVITY_PAUSED
|| event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED
|| event == Event.ACTIVITY_DESTROYED)) {
@@ -2626,19 +2737,18 @@
"getPackageProcessState");
}
- int procState = PROCESS_STATE_NONEXISTENT;
- synchronized (this) {
- for (int i=mProcessList.mLruProcesses.size()-1; i>=0; i--) {
- final ProcessRecord proc = mProcessList.mLruProcesses.get(i);
- if (procState > proc.setProcState) {
- if (proc.getPkgList().containsKey(packageName)
- || (proc.pkgDeps != null && proc.pkgDeps.contains(packageName))) {
- procState = proc.setProcState;
+ final int[] procState = {PROCESS_STATE_NONEXISTENT};
+ synchronized (mProcLock) {
+ mProcessList.forEachLruProcessesLOSP(false, proc -> {
+ if (procState[0] > proc.mState.getSetProcState()) {
+ if (proc.getPkgList().containsKey(packageName) || (proc.getPkgDeps() != null
+ && proc.getPkgDeps().contains(packageName))) {
+ procState[0] = proc.mState.getSetProcState();
}
}
- }
+ });
}
- return procState;
+ return procState[0];
}
@Override
@@ -2648,11 +2758,12 @@
throw new SecurityException("Only shell can call it");
}
synchronized (this) {
- final ProcessRecord app = findProcessLocked(process, userId, "setProcessMemoryTrimLevel");
+ final ProcessRecord app = findProcessLOSP(process, userId, "setProcessMemoryTrimLevel");
if (app == null) {
throw new IllegalArgumentException("Unknown process: " + process);
}
- if (app.thread == null) {
+ final IApplicationThread thread = app.getThread();
+ if (thread == null) {
throw new IllegalArgumentException("Process has no app thread");
}
if (app.mProfile.getTrimMemoryLevel() >= level) {
@@ -2660,12 +2771,14 @@
"Unable to set a higher trim level than current level");
}
if (!(level < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN ||
- app.getCurProcState() > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)) {
+ app.mState.getCurProcState() > PROCESS_STATE_IMPORTANT_FOREGROUND)) {
throw new IllegalArgumentException("Unable to set a background trim level "
+ "on a foreground process");
}
- app.thread.scheduleTrimMemory(level);
- app.mProfile.setTrimMemoryLevel(level);
+ thread.scheduleTrimMemory(level);
+ synchronized (mProcLock) {
+ app.mProfile.setTrimMemoryLevel(level);
+ }
return true;
}
}
@@ -2822,10 +2935,9 @@
* to the process.
*/
@GuardedBy("this")
- final void handleAppDiedLocked(ProcessRecord app,
+ final void handleAppDiedLocked(ProcessRecord app, int pid,
boolean restarting, boolean allowRestart) {
- int pid = app.pid;
- boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1,
+ boolean kept = cleanUpApplicationRecordLocked(app, pid, restarting, allowRestart, -1,
false /*replacingPid*/);
if (!kept && !restarting) {
removeLruProcessLocked(app);
@@ -2845,24 +2957,26 @@
});
}
- ProcessRecord getRecordForAppLocked(IApplicationThread thread) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ ProcessRecord getRecordForAppLOSP(IApplicationThread thread) {
if (thread == null) {
return null;
}
- ProcessRecord record = mProcessList.getLRURecordForAppLocked(thread);
+ ProcessRecord record = mProcessList.getLRURecordForAppLOSP(thread);
if (record != null) return record;
// Validation: if it isn't in the LRU list, it shouldn't exist, but let's
// double-check that.
final IBinder threadBinder = thread.asBinder();
final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
- mProcessList.mProcessNames.getMap();
+ mProcessList.getProcessNamesLOSP().getMap();
for (int i = pmap.size()-1; i >= 0; i--) {
final SparseArray<ProcessRecord> procs = pmap.valueAt(i);
for (int j = procs.size()-1; j >= 0; j--) {
final ProcessRecord proc = procs.valueAt(j);
- if (proc.thread != null && proc.thread.asBinder() == threadBinder) {
+ final IApplicationThread procThread = proc.getThread();
+ if (procThread != null && procThread.asBinder() == threadBinder) {
Slog.wtf(TAG, "getRecordForApp: exists in name list but not in LRU list: "
+ proc);
return proc;
@@ -2875,7 +2989,7 @@
@GuardedBy("this")
final void appDiedLocked(ProcessRecord app, String reason) {
- appDiedLocked(app, app.pid, app.thread, false, reason);
+ appDiedLocked(app, app.getPid(), app.getThread(), false, reason);
}
@GuardedBy("this")
@@ -2892,26 +3006,31 @@
mBatteryStatsService.noteProcessDied(app.info.uid, pid);
- if (!app.killed) {
+ if (!app.isKilled()) {
if (!fromBinderDied) {
killProcessQuiet(pid);
mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
}
ProcessList.killProcessGroup(app.uid, pid);
- app.killed = true;
+ synchronized (mProcLock) {
+ app.setKilled(true);
+ }
}
// Clean up already done if the process has been re-started.
- if (app.pid == pid && app.thread != null &&
- app.thread.asBinder() == thread.asBinder()) {
+ IApplicationThread appThread;
+ final int setAdj = app.mState.getSetAdj();
+ final int setProcState = app.mState.getSetProcState();
+ if (app.getPid() == pid && (appThread = app.getThread()) != null
+ && appThread.asBinder() == thread.asBinder()) {
boolean doLowMem = app.getActiveInstrumentation() == null;
boolean doOomAdj = doLowMem;
- if (!app.killedByAm) {
+ if (!app.isKilledByAm()) {
reportUidInfoMessageLocked(TAG,
"Process " + app.processName + " (pid " + pid + ") has died: "
- + ProcessList.makeOomAdjString(app.setAdj, true) + " "
- + ProcessList.makeProcStateString(app.setProcState), app.info.uid);
+ + ProcessList.makeOomAdjString(setAdj, true) + " "
+ + ProcessList.makeProcStateString(setProcState), app.info.uid);
mAppProfiler.setAllowLowerMemLevelLocked(true);
} else {
// Note that we always want to do oom adj to update our state with the
@@ -2919,11 +3038,10 @@
mAppProfiler.setAllowLowerMemLevelLocked(false);
doLowMem = false;
}
- EventLogTags.writeAmProcDied(app.userId, app.pid, app.processName, app.setAdj,
- app.setProcState);
+ EventLogTags.writeAmProcDied(app.userId, pid, app.processName, setAdj, setProcState);
if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
"Dying app: " + app + ", pid: " + pid + ", thread: " + thread.asBinder());
- handleAppDiedLocked(app, false, true);
+ handleAppDiedLocked(app, pid, false, true);
if (doOomAdj) {
updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
@@ -2931,14 +3049,14 @@
if (doLowMem) {
mAppProfiler.doLowMemReportIfNeededLocked(app);
}
- } else if (app.pid != pid) {
+ } else if (app.getPid() != pid) {
// A new process has already been started.
reportUidInfoMessageLocked(TAG,
"Process " + app.processName + " (pid " + pid
- + ") has died and restarted (pid " + app.pid + ").", app.info.uid);
+ + ") has died and restarted (pid " + app.getPid() + ").", app.info.uid);
- EventLogTags.writeAmProcDied(app.userId, app.pid, app.processName, app.setAdj,
- app.setProcState);
+ EventLogTags.writeAmProcDied(app.userId, app.getPid(), app.processName,
+ setAdj, setProcState);
} else if (DEBUG_PROCESSES) {
Slog.d(TAG_PROCESSES, "Received spurious death notification for thread "
+ thread.asBinder());
@@ -3386,9 +3504,11 @@
return;
}
synchronized (this) {
- mProcessList.killPackageProcessesLocked(packageName, appId, targetUserId,
- ProcessList.SERVICE_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN, "kill background");
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(packageName, appId, targetUserId,
+ ProcessList.SERVICE_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN, "kill background");
+ }
}
}
} finally {
@@ -3413,11 +3533,13 @@
// Allow memory level to go down (the flag needs to be set before updating oom adj)
// because this method is also used to simulate low memory.
mAppProfiler.setAllowLowerMemLevelLocked(true);
- mProcessList.killPackageProcessesLocked(null /* packageName */, -1 /* appId */,
- UserHandle.USER_ALL, ProcessList.CACHED_APP_MIN_ADJ,
- ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- "kill all background");
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(null /* packageName */, -1 /* appId */,
+ UserHandle.USER_ALL, ProcessList.CACHED_APP_MIN_ADJ,
+ ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "kill all background");
+ }
mAppProfiler.doLowMemReportIfNeededLocked(null);
}
@@ -3448,7 +3570,9 @@
final long callingId = Binder.clearCallingIdentity();
try {
synchronized (this) {
- mProcessList.killAllBackgroundProcessesExceptLocked(minTargetSdk, maxProcState);
+ synchronized (mProcLock) {
+ mProcessList.killAllBackgroundProcessesExceptLSP(minTargetSdk, maxProcState);
+ }
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -3523,11 +3647,14 @@
proc = mPidsSelfLocked.get(Binder.getCallingPid());
}
if (proc != null) {
+ ArraySet<String> pkgDeps = proc.getPkgDeps();
synchronized (this) {
- if (proc.pkgDeps == null) {
- proc.pkgDeps = new ArraySet<String>(1);
+ synchronized (mProcLock) {
+ if (pkgDeps == null) {
+ proc.setPkgDeps(pkgDeps = new ArraySet<String>(1));
+ }
+ pkgDeps.add(packageName);
}
- proc.pkgDeps.add(packageName);
}
}
}
@@ -3587,12 +3714,14 @@
// Check if the caller is actually instrumented and from shell, if it's true, we may lift
// the throttle of PSS info sampling.
boolean isCallerInstrumentedFromShell = false;
- synchronized (mPidsSelfLocked) {
- ProcessRecord caller = mPidsSelfLocked.get(callingPid);
- if (caller != null) {
- final ActiveInstrumentation instr = caller.getActiveInstrumentation();
- isCallerInstrumentedFromShell = instr != null
- && (instr.mSourceUid == SHELL_UID || instr.mSourceUid == ROOT_UID);
+ synchronized (mProcLock) {
+ synchronized (mPidsSelfLocked) {
+ ProcessRecord caller = mPidsSelfLocked.get(callingPid);
+ if (caller != null) {
+ final ActiveInstrumentation instr = caller.getActiveInstrumentation();
+ isCallerInstrumentedFromShell = instr != null
+ && (instr.mSourceUid == SHELL_UID || instr.mSourceUid == ROOT_UID);
+ }
}
}
@@ -3687,10 +3816,10 @@
for (int i=pids.length-1; i>=0; i--) {
ProcessRecord proc;
int oomAdj;
- synchronized (this) {
+ synchronized (mProcLock) {
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(pids[i]);
- oomAdj = proc != null ? proc.setAdj : 0;
+ oomAdj = proc != null ? proc.mState.getSetAdj() : 0;
}
}
if (!allUids || (!allUsers && UserHandle.getUserId(proc.uid) != userId)) {
@@ -3739,9 +3868,10 @@
if (callerUid == SYSTEM_UID) {
synchronized (this) {
ProcessRecord app = getProcessRecordLocked(processName, uid, true);
- if (app != null && app.thread != null) {
+ IApplicationThread thread;
+ if (app != null && (thread = app.getThread()) != null) {
try {
- app.thread.scheduleSuicide();
+ thread.scheduleSuicide();
} catch (RemoteException e) {
// If the other end already died, then our work here is done.
}
@@ -3903,6 +4033,7 @@
}
}
+ boolean didSomething;
if (doit) {
if (packageName != null) {
Slog.i(TAG, "Force stopping " + packageName + " appid=" + appId
@@ -3914,20 +4045,22 @@
mAppErrors.resetProcessCrashTime(packageName == null, appId, userId);
}
- // Notify first that the package is stopped, so its process won't be restarted unexpectedly
- // if there is an activity of the package without attached process becomes visible when
- // killing its other processes with visible activities.
- boolean didSomething =
- mAtmInternal.onForceStopPackage(packageName, doit, evenPersistent, userId);
+ synchronized (mProcLock) {
+ // Notify first that the package is stopped, so its process won't be restarted
+ // unexpectedly if there is an activity of the package without attached process
+ // becomes visible when killing its other processes with visible activities.
+ didSomething = mAtmInternal.onForceStopPackage(
+ packageName, doit, evenPersistent, userId);
- didSomething |= mProcessList.killPackageProcessesLocked(packageName, appId, userId,
- ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
- evenPersistent, true /* setRemoved */,
- packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
- : ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- (packageName == null ? ("stop user " + userId) : ("stop " + packageName))
- + " due to " + reason);
+ didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId,
+ ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
+ evenPersistent, true /* setRemoved */,
+ packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
+ : ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ (packageName == null ? ("stop user " + userId) : ("stop " + packageName))
+ + " due to " + reason);
+ }
if (mServices.bringDownDisabledPackageServicesLocked(
packageName, null /* filterByClasses */, userId, evenPersistent, doit)) {
@@ -3986,26 +4119,29 @@
@GuardedBy("this")
private final void processStartTimedOutLocked(ProcessRecord app) {
- final int pid = app.pid;
- boolean gone = removePidIfNoThread(app);
+ final int pid = app.getPid();
+ boolean gone = removePidIfNoThreadLocked(app);
if (gone) {
Slog.w(TAG, "Process " + app + " failed to attach");
EventLogTags.writeAmProcessStartTimeout(app.userId, pid, app.uid, app.processName);
- mProcessList.removeProcessNameLocked(app.processName, app.uid);
- mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
- mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
- // Take care of any launching providers waiting for this process.
- mCpHelper.cleanupAppInLaunchingProvidersLocked(app, true);
- // Take care of any services that are waiting for the process.
- mServices.processStartTimedOutLocked(app);
- app.kill("start timeout", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
- if (app.isolated) {
- mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+ synchronized (mProcLock) {
+ mProcessList.removeProcessNameLocked(app.processName, app.uid);
+ mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
+ mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
+ // Take care of any launching providers waiting for this process.
+ mCpHelper.cleanupAppInLaunchingProvidersLocked(app, true);
+ // Take care of any services that are waiting for the process.
+ mServices.processStartTimedOutLocked(app);
+ app.killLocked("start timeout", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+ true);
+ if (app.isolated) {
+ mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+ }
+ removeLruProcessLocked(app);
}
- removeLruProcessLocked(app);
final BackupRecord backupTarget = mBackupTargets.get(app.userId);
- if (backupTarget != null && backupTarget.app.pid == pid) {
+ if (backupTarget != null && backupTarget.app.getPid() == pid) {
Slog.w(TAG, "Unattached app died before backup, skipping");
mHandler.post(new Runnable() {
@Override
@@ -4043,7 +4179,7 @@
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
- if (app != null && (app.startUid != callingUid || app.startSeq != startSeq)) {
+ if (app != null && (app.getStartUid() != callingUid || app.getStartSeq() != startSeq)) {
String processName = null;
final ProcessRecord pending = mProcessList.mPendingStarts.get(startSeq);
if (pending != null) {
@@ -4053,14 +4189,14 @@
+ " startSeq:" + startSeq
+ " pid:" + pid
+ " belongs to another existing app:" + app.processName
- + " startSeq:" + app.startSeq;
+ + " startSeq:" + app.getStartSeq();
Slog.wtf(TAG, msg);
// SafetyNet logging for b/131105245.
- EventLog.writeEvent(0x534e4554, "131105245", app.startUid, msg);
+ EventLog.writeEvent(0x534e4554, "131105245", app.getStartUid(), msg);
// If there is already an app occupying that pid that hasn't been cleaned up
- cleanUpApplicationRecordLocked(app, false, false, -1,
- true /*replacingPid*/);
- removePidLocked(app);
+ cleanUpApplicationRecordLocked(app, pid, false, false, -1,
+ true /*replacingPid*/);
+ removePidLocked(pid, app);
app = null;
}
} else {
@@ -4071,10 +4207,10 @@
// update the internal state.
if (app == null && startSeq > 0) {
final ProcessRecord pending = mProcessList.mPendingStarts.get(startSeq);
- if (pending != null && pending.startUid == callingUid && pending.startSeq == startSeq
- && mProcessList.handleProcessStartedLocked(pending, pid, pending
- .isUsingWrapper(),
- startSeq, true)) {
+ if (pending != null && pending.getStartUid() == callingUid
+ && pending.getStartSeq() == startSeq
+ && mProcessList.handleProcessStartedLocked(pending, pid,
+ pending.isUsingWrapper(), startSeq, true)) {
app = pending;
}
}
@@ -4100,8 +4236,8 @@
// If this application record is still attached to a previous
// process, clean it up now.
- if (app.thread != null) {
- handleAppDiedLocked(app, true, true);
+ if (app.getThread() != null) {
+ handleAppDiedLocked(app, pid, true, true);
}
// Tell the process all about itself.
@@ -4114,7 +4250,7 @@
AppDeathRecipient adr = new AppDeathRecipient(
app, pid, thread);
thread.asBinder().linkToDeath(adr, 0);
- app.deathRecipient = adr;
+ app.setDeathRecipient(adr);
} catch (RemoteException e) {
app.resetPackageList(mProcessStats);
mProcessList.startProcessLocked(app,
@@ -4123,23 +4259,25 @@
return false;
}
- EventLogTags.writeAmProcBound(app.userId, app.pid, app.processName);
+ EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
- app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;
- mOomAdjuster.setAttachingSchedGroupLocked(app);
- app.forcingToImportant = null;
- updateProcessForegroundLocked(app, false, 0, false);
- app.hasShownUi = false;
- app.setDebugging(false);
- app.setCached(false);
- app.killedByAm = false;
- app.killed = false;
-
-
- // We carefully use the same state that PackageManager uses for
- // filtering, since we use this flag to decide if we need to install
- // providers when user is unlocked later
- app.unlocked = StorageManager.isUserKeyUnlocked(app.userId);
+ synchronized (mProcLock) {
+ app.mState.setCurAdj(ProcessList.INVALID_ADJ);
+ app.mState.setSetAdj(ProcessList.INVALID_ADJ);
+ app.mState.setVerifiedAdj(ProcessList.INVALID_ADJ);
+ mOomAdjuster.setAttachingSchedGroupLSP(app);
+ app.mState.setForcingToImportant(null);
+ updateProcessForegroundLocked(app, false, 0, false);
+ app.mState.setHasShownUi(false);
+ app.mState.setCached(false);
+ app.setDebugging(false);
+ app.setKilledByAm(false);
+ app.setKilled(false);
+ // We carefully use the same state that PackageManager uses for
+ // filtering, since we use this flag to decide if we need to install
+ // providers when user is unlocked later
+ app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
+ }
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
@@ -4179,9 +4317,11 @@
}
boolean enableTrackAllocation = false;
- if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
- enableTrackAllocation = true;
- mTrackAllocationApp = null;
+ synchronized (mProcLock) {
+ if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
+ enableTrackAllocation = true;
+ mTrackAllocationApp = null;
+ }
}
// If the app is being launched for restore or full backup, set it up specially
@@ -4202,7 +4342,7 @@
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Binding proc %s with config %s",
processName, app.getWindowProcessController().getConfiguration());
ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info;
- app.compat = compatibilityInfoForPackage(appInfo);
+ app.setCompat(compatibilityInfoForPackage(appInfo));
ProfilerInfo profilerInfo = mAppProfiler.setupProfilerInfoLocked(thread, app, instr);
@@ -4246,10 +4386,11 @@
mPlatformCompat.resetReporting(app.info);
}
final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
- if (app.isolatedEntryPoint != null) {
+ if (app.getIsolatedEntryPoint() != null) {
// This is an isolated process which should just call an entry point instead of
// being bound to an application.
- thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
+ thread.runIsolatedEntryPoint(
+ app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
} else if (instr2 != null) {
thread.bindApplication(processName, appInfo, providerList,
instr2.mClass,
@@ -4259,20 +4400,20 @@
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
- app.compat, getCommonServicesLocked(app.isolated),
+ app.getCompat(), getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
- app.mDisabledCompatChanges, serializedSystemFontMap);
+ app.getDisabledCompatChanges(), serializedSystemFontMap);
} else {
thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
- app.compat, getCommonServicesLocked(app.isolated),
+ app.getCompat(), getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
- app.mDisabledCompatChanges, serializedSystemFontMap);
+ app.getDisabledCompatChanges(), serializedSystemFontMap);
}
if (profilerInfo != null) {
profilerInfo.closeFd();
@@ -4281,9 +4422,11 @@
// Make app active after binding application or client may be running requests (e.g
// starting activities) before it is ready.
- app.makeActive(thread, mProcessStats);
- checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
- mProcessList.updateLruProcessLocked(app, false, null);
+ synchronized (mProcLock) {
+ app.makeActive(thread, mProcessStats);
+ checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
+ }
+ updateLruProcessLocked(app, false, null);
checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
final long now = SystemClock.uptimeMillis();
synchronized (mAppProfiler.mProfilerLock) {
@@ -4295,8 +4438,9 @@
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
app.resetPackageList(mProcessStats);
app.unlinkDeathRecipient();
- app.kill("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
- handleAppDiedLocked(app, false, true);
+ app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+ true);
+ handleAppDiedLocked(app, pid, false, true);
return false;
}
@@ -4359,8 +4503,9 @@
}
if (badApp) {
- app.kill("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
- handleAppDiedLocked(app, false, true);
+ app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+ true);
+ handleAppDiedLocked(app, pid, false, true);
return false;
}
@@ -4372,14 +4517,14 @@
FrameworkStatsLog.write(
FrameworkStatsLog.PROCESS_START_TIME,
app.info.uid,
- app.pid,
+ pid,
app.info.packageName,
FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
- app.startTime,
- (int) (bindApplicationTimeMillis - app.startTime),
- (int) (SystemClock.elapsedRealtime() - app.startTime),
- app.hostingRecord.getType(),
- (app.hostingRecord.getName() != null ? app.hostingRecord.getName() : ""));
+ app.getStartTime(),
+ (int) (bindApplicationTimeMillis - app.getStartTime()),
+ (int) (SystemClock.elapsedRealtime() - app.getStartTime()),
+ app.getHostingRecord().getType(),
+ (app.getHostingRecord().getName() != null ? app.getHostingRecord().getName() : ""));
return true;
}
@@ -4472,11 +4617,11 @@
// up.
final int NP = mProcessesOnHold.size();
if (NP > 0) {
- ArrayList<ProcessRecord> procs =
- new ArrayList<ProcessRecord>(mProcessesOnHold);
- for (int ip=0; ip<NP; ip++) {
- if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "Starting process on hold: "
- + procs.get(ip));
+ ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(mProcessesOnHold);
+ for (int ip = 0; ip < NP; ip++) {
+ if (DEBUG_PROCESSES) {
+ Slog.v(TAG_PROCESSES, "Starting process on hold: " + procs.get(ip));
+ }
mProcessList.startProcessLocked(procs.get(ip),
new HostingRecord("on-hold"),
ZYGOTE_POLICY_FLAG_BATCH_LAUNCH);
@@ -4508,8 +4653,8 @@
public void performReceive(Intent intent, int resultCode,
String data, Bundle extras, boolean ordered,
boolean sticky, int sendingUser) {
- synchronized (ActivityManagerService.this) {
- mAppProfiler.requestPssAllProcsLocked(
+ synchronized (mProcLock) {
+ mAppProfiler.requestPssAllProcsLPr(
SystemClock.uptimeMillis(), true, false);
}
}
@@ -4944,7 +5089,7 @@
if (pr == null) {
return;
}
- pr.forcingToImportant = null;
+ pr.mState.setForcingToImportant(null);
updateProcessForegroundLocked(pr, false, 0, false);
}
updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
@@ -4970,7 +5115,7 @@
oldToken.token.unlinkToDeath(oldToken, 0);
mImportantProcesses.remove(pid);
if (pr != null) {
- pr.forcingToImportant = null;
+ pr.mState.setForcingToImportant(null);
}
changed = true;
}
@@ -4984,7 +5129,7 @@
try {
token.linkToDeath(newToken, 0);
mImportantProcesses.put(pid, newToken);
- pr.forcingToImportant = newToken;
+ pr.mState.setForcingToImportant(newToken);
changed = true;
} catch (RemoteException e) {
// If the process died while doing this, we will later
@@ -5000,13 +5145,12 @@
}
private boolean isAppForeground(int uid) {
- synchronized (this) {
+ synchronized (mProcLock) {
UidRecord uidRec = mProcessList.mActiveUids.get(uid);
- if (uidRec == null || uidRec.idle) {
+ if (uidRec == null || uidRec.isIdle()) {
return false;
}
- return uidRec.getCurProcState()
- <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ return uidRec.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
}
}
@@ -5017,11 +5161,16 @@
// NOTE: this is an internal method used by the OnShellCommand implementation only and should
// be guarded by permission checking.
int getUidState(int uid) {
- synchronized (this) {
- return mProcessList.getUidProcStateLocked(uid);
+ synchronized (mProcLock) {
+ return mProcessList.getUidProcStateLOSP(uid);
}
}
+ @GuardedBy("this")
+ int getUidStateLocked(int uid) {
+ return mProcessList.getUidProcStateLOSP(uid);
+ }
+
// =========================================================
// PROCESS INFO
// =========================================================
@@ -5070,20 +5219,23 @@
throw new IllegalArgumentException("pids and scores arrays have different lengths!");
}
- synchronized (mPidsSelfLocked) {
- for (int i = 0; i < pids.length; i++) {
- ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
- if (pr != null) {
- final boolean isPendingTop =
+ synchronized (mProcLock) {
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < pids.length; i++) {
+ ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
+ if (pr != null) {
+ final boolean isPendingTop =
mPendingStartActivityUids.isPendingTopPid(pr.uid, pids[i]);
- states[i] = isPendingTop ? PROCESS_STATE_TOP : pr.getCurProcState();
- if (scores != null) {
- scores[i] = isPendingTop ? (ProcessList.FOREGROUND_APP_ADJ - 1) : pr.curAdj;
- }
- } else {
- states[i] = PROCESS_STATE_NONEXISTENT;
- if (scores != null) {
- scores[i] = ProcessList.INVALID_ADJ;
+ states[i] = isPendingTop ? PROCESS_STATE_TOP : pr.mState.getCurProcState();
+ if (scores != null) {
+ scores[i] = isPendingTop
+ ? (ProcessList.FOREGROUND_APP_ADJ - 1) : pr.mState.getCurAdj();
+ }
+ } else {
+ states[i] = PROCESS_STATE_NONEXISTENT;
+ if (scores != null) {
+ scores[i] = ProcessList.INVALID_ADJ;
+ }
}
}
}
@@ -5261,8 +5413,8 @@
}
public boolean isAppStartModeDisabled(int uid, String packageName) {
- synchronized (this) {
- return getAppStartModeLocked(uid, packageName, 0, -1, false, true, false)
+ synchronized (mProcLock) {
+ return getAppStartModeLOSP(uid, packageName, 0, -1, false, true, false)
== ActivityManager.APP_START_MODE_DISABLED;
}
}
@@ -5273,7 +5425,8 @@
}
// Unified app-op and target sdk check
- int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ int appRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
// Apps that target O+ are always subject to background check
if (packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
@@ -5302,7 +5455,7 @@
// If force-background-check is enabled, restrict all apps that aren't whitelisted.
if (mForceBackgroundCheck &&
!UserHandle.isCore(uid) &&
- !isOnDeviceIdleAllowlistLocked(uid, /*allowExceptIdleToo=*/ true)) {
+ !isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ true)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Force background check: " +
uid + "/" + packageName + " restricted");
@@ -5320,7 +5473,8 @@
// Service launch is available to apps with run-in-background exemptions but
// some other background operations are not. If we're doing a check
// of service-launch policy, allow those callers to proceed unrestricted.
- int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ int appServicesRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
// Persistent app?
if (mPackageManagerInt.isPackagePersistent(packageName)) {
if (DEBUG_BACKGROUND_CHECK) {
@@ -5331,7 +5485,7 @@
}
// Non-persistent but background whitelisted?
- if (uidOnBackgroundAllowlist(uid)) {
+ if (uidOnBackgroundAllowlistLOSP(uid)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " on background whitelist; not restricted in background");
@@ -5340,7 +5494,7 @@
}
// Is this app on the battery whitelist?
- if (isOnDeviceIdleAllowlistLocked(uid, /*allowExceptIdleToo=*/ false)) {
+ if (isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ false)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " on idle whitelist; not restricted in background");
@@ -5349,25 +5503,26 @@
}
// None of the service-policy criteria apply, so we apply the common criteria
- return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
+ return appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk);
}
- int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ int getAppStartModeLOSP(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
if (mInternal.isPendingTopUid(uid)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
- UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
+ UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
- + (uidRec != null ? uidRec.idle : false));
- if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) {
+ + (uidRec != null ? uidRec.isIdle() : false));
+ if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.isIdle()) {
boolean ephemeral;
if (uidRec == null) {
ephemeral = getPackageManagerInternal().isPackageEphemeral(
UserHandle.getUserId(uid), packageName);
} else {
- ephemeral = uidRec.ephemeral;
+ ephemeral = uidRec.isEphemeral();
}
if (ephemeral) {
@@ -5382,14 +5537,14 @@
return ActivityManager.APP_START_MODE_NORMAL;
}
final int startMode = (alwaysRestrict)
- ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
- : appServicesRestrictedInBackgroundLocked(uid, packageName,
+ ? appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk)
+ : appServicesRestrictedInBackgroundLOSP(uid, packageName,
packageTargetSdk);
if (DEBUG_BACKGROUND_CHECK) {
Slog.d(TAG, "checkAllowBackground: uid=" + uid
+ " pkg=" + packageName + " startMode=" + startMode
- + " onallowlist=" + isOnDeviceIdleAllowlistLocked(uid, false)
- + " onallowlist(ei)=" + isOnDeviceIdleAllowlistLocked(uid, true));
+ + " onallowlist=" + isOnDeviceIdleAllowlistLOSP(uid, false)
+ + " onallowlist(ei)=" + isOnDeviceIdleAllowlistLOSP(uid, true));
}
if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
// This is an old app that has been forced into a "compatible as possible"
@@ -5400,8 +5555,8 @@
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(callingPid);
}
- if (proc != null &&
- !ActivityManager.isProcStateBackground(proc.getCurProcState())) {
+ if (proc != null && !ActivityManager.isProcStateBackground(
+ proc.mState.getCurProcState())) {
// Whoever is instigating this is in the foreground, so we will allow it
// to go through.
return ActivityManager.APP_START_MODE_NORMAL;
@@ -5417,7 +5572,8 @@
/**
* @return whether a UID is in the system, user or temp doze allowlist.
*/
- boolean isOnDeviceIdleAllowlistLocked(int uid, boolean allowExceptIdleToo) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ boolean isOnDeviceIdleAllowlistLOSP(int uid, boolean allowExceptIdleToo) {
final int appId = UserHandle.getAppId(uid);
final int[] allowlist = allowExceptIdleToo
@@ -5429,7 +5585,8 @@
|| mPendingTempAllowlist.indexOfKey(uid) >= 0;
}
- boolean isAllowlistedForFgsStartLocked(int uid) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ boolean isAllowlistedForFgsStartLOSP(int uid) {
return Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, UserHandle.getAppId(uid)) >= 0
|| mFgsStartTempAllowList.isAllowed(uid);
}
@@ -5438,7 +5595,8 @@
* @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on
* the allowlist
*/
- String getPendingTempAllowlistTagForUidLocked(int uid) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ String getPendingTempAllowlistTagForUidLOSP(int uid) {
final PendingTempAllowlist ptw = mPendingTempAllowlist.get(uid);
return ptw != null ? ptw.tag : null;
}
@@ -5483,8 +5641,8 @@
final int modeFlags, int userId) {
enforceNotIsolatedCaller("grantUriPermission");
GrantUri grantUri = new GrantUri(userId, uri, modeFlags);
- synchronized(this) {
- final ProcessRecord r = getRecordForAppLocked(caller);
+ synchronized (mProcLock) {
+ final ProcessRecord r = getRecordForAppLOSP(caller);
if (r == null) {
throw new SecurityException("Unable to find app for caller "
+ caller
@@ -5517,8 +5675,8 @@
public void revokeUriPermission(IApplicationThread caller, String targetPackage, Uri uri,
final int modeFlags, int userId) {
enforceNotIsolatedCaller("revokeUriPermission");
- synchronized(this) {
- final ProcessRecord r = getRecordForAppLocked(caller);
+ synchronized (mProcLock) {
+ final ProcessRecord r = getRecordForAppLOSP(caller);
if (r == null) {
throw new SecurityException("Unable to find app for caller "
+ caller
@@ -5549,9 +5707,8 @@
@Override
public void showWaitingForDebugger(IApplicationThread who, boolean waiting) {
- synchronized (this) {
- ProcessRecord app =
- who != null ? getRecordForAppLocked(who) : null;
+ synchronized (mProcLock) {
+ final ProcessRecord app = who != null ? getRecordForAppLOSP(who) : null;
if (app == null) return;
Message msg = Message.obtain();
@@ -5850,7 +6007,8 @@
// GLOBAL MANAGEMENT
// =========================================================
- private boolean uidOnBackgroundAllowlist(final int uid) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ private boolean uidOnBackgroundAllowlistLOSP(final int uid) {
final int appId = UserHandle.getAppId(uid);
final int[] allowlist = mBackgroundAppIdAllowlist;
for (int i = 0, len = allowlist.length; i < len; i++) {
@@ -5894,11 +6052,13 @@
Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist");
}
synchronized (this) {
- final int num = mBackgroundAppIdAllowlist.length;
- int[] newList = new int[num + 1];
- System.arraycopy(mBackgroundAppIdAllowlist, 0, newList, 0, num);
- newList[num] = UserHandle.getAppId(uid);
- mBackgroundAppIdAllowlist = newList;
+ synchronized (mProcLock) {
+ final int num = mBackgroundAppIdAllowlist.length;
+ int[] newList = new int[num + 1];
+ System.arraycopy(mBackgroundAppIdAllowlist, 0, newList, 0, num);
+ newList[num] = UserHandle.getAppId(uid);
+ mBackgroundAppIdAllowlist = newList;
+ }
}
}
@@ -5909,18 +6069,10 @@
abiOverride, zygotePolicyFlags);
}
- @GuardedBy("this")
- final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
- boolean disableHiddenApiChecks, String abiOverride, int zygotePolicyFlags) {
- return addAppLocked(info, customProcess, isolated, disableHiddenApiChecks,
- false /* disableTestApiChecks */, abiOverride, zygotePolicyFlags);
- }
-
// TODO: Move to ProcessList?
@GuardedBy("this")
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
- boolean disableHiddenApiChecks, boolean disableTestApiChecks,
- String abiOverride, int zygotePolicyFlags) {
+ boolean disableHiddenApiChecks, String abiOverride, int zygotePolicyFlags) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@@ -5932,8 +6084,8 @@
if (app == null) {
app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0,
new HostingRecord("added application",
- customProcess != null ? customProcess : info.processName));
- mProcessList.updateLruProcessLocked(app, false, null);
+ customProcess != null ? customProcess : info.processName));
+ updateLruProcessLocked(app, false, null);
updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
}
@@ -5949,13 +6101,13 @@
if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
app.setPersistent(true);
- app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
+ app.mState.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
}
- if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
+ if (app.getThread() == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
mProcessList.startProcessLocked(app, new HostingRecord("added application",
customProcess != null ? customProcess : app.processName),
- zygotePolicyFlags, disableHiddenApiChecks, disableTestApiChecks, abiOverride);
+ zygotePolicyFlags, disableHiddenApiChecks, abiOverride);
}
return app;
@@ -6022,7 +6174,7 @@
}
void onWakefulnessChanged(int wakefulness) {
- synchronized(this) {
+ synchronized (this) {
boolean wasAwake = mWakefulness.getAndSet(wakefulness)
== PowerManagerInternal.WAKEFULNESS_AWAKE;
boolean isAwake = wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
@@ -6149,13 +6301,13 @@
}
void setTrackAllocationApp(ApplicationInfo app, String processName) {
- synchronized (this) {
- if (!Build.IS_DEBUGGABLE) {
- if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
- throw new SecurityException("Process not debuggable: " + app.packageName);
- }
+ if (!Build.IS_DEBUGGABLE) {
+ if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+ throw new SecurityException("Process not debuggable: " + app.packageName);
}
+ }
+ synchronized (mProcLock) {
mTrackAllocationApp = processName;
}
}
@@ -6212,7 +6364,7 @@
@Override
public void setUserIsMonkey(boolean userIsMonkey) {
- synchronized (this) {
+ synchronized (mProcLock) {
synchronized (mPidsSelfLocked) {
final int callingPid = Binder.getCallingPid();
ProcessRecord proc = mPidsSelfLocked.get(callingPid);
@@ -6231,7 +6383,7 @@
@Override
public boolean isUserAMonkey() {
- synchronized (this) {
+ synchronized (mProcLock) {
// If there is a controller also implies the user is a monkey.
return mUserIsMonkey || mActivityTaskManager.isControllerAMonkey();
}
@@ -6452,8 +6604,8 @@
"getUidProcessState");
}
- synchronized (this) {
- return mProcessList.getUidProcStateLocked(uid);
+ synchronized (mProcLock) {
+ return mProcessList.getUidProcStateLOSP(uid);
}
}
@@ -6479,17 +6631,18 @@
enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
"isUidActive");
}
- synchronized (this) {
- if (isUidActiveLocked(uid)) {
+ synchronized (mProcLock) {
+ if (isUidActiveLOSP(uid)) {
return true;
}
}
return mInternal.isPendingTopUid(uid);
}
- boolean isUidActiveLocked(int uid) {
- final UidRecord uidRecord = mProcessList.getUidRecordLocked(uid);
- return uidRecord != null && !uidRecord.setIdle;
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ boolean isUidActiveLOSP(int uid) {
+ final UidRecord uidRecord = mProcessList.getUidRecordLOSP(uid);
+ return uidRecord != null && !uidRecord.isSetIdle();
}
@Override
@@ -6547,7 +6700,7 @@
@Override
public void setRenderThread(int tid) {
- synchronized (this) {
+ synchronized (mProcLock) {
ProcessRecord proc;
int pid = Binder.getCallingPid();
if (pid == Process.myPid()) {
@@ -6556,32 +6709,31 @@
}
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(pid);
- if (proc != null && proc.renderThreadTid == 0 && tid > 0) {
- // ensure the tid belongs to the process
- if (!isThreadInProcess(pid, tid)) {
- throw new IllegalArgumentException(
+ }
+ if (proc != null && proc.getRenderThreadTid() == 0 && tid > 0) {
+ // ensure the tid belongs to the process
+ if (!isThreadInProcess(pid, tid)) {
+ throw new IllegalArgumentException(
"Render thread does not belong to process");
- }
- proc.renderThreadTid = tid;
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Set RenderThread tid " + tid + " for pid " + pid);
- }
- // promote to FIFO now
- if (proc.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
- if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
- if (mUseFifoUiScheduling) {
- setThreadScheduler(proc.renderThreadTid,
+ }
+ proc.setRenderThreadTid(tid);
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Set RenderThread tid " + tid + " for pid " + pid);
+ }
+ // promote to FIFO now
+ if (proc.mState.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
+ if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
+ if (mUseFifoUiScheduling) {
+ setThreadScheduler(proc.getRenderThreadTid(),
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
- } else {
- setThreadPriority(proc.renderThreadTid, TOP_APP_PRIORITY_BOOST);
- }
+ } else {
+ setThreadPriority(proc.getRenderThreadTid(), TOP_APP_PRIORITY_BOOST);
}
- } else {
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Didn't set thread from setRenderThread? " +
- "PID: " + pid + ", TID: " + tid + " FIFO: " +
- mUseFifoUiScheduling);
- }
+ }
+ } else {
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Didn't set thread from setRenderThread? "
+ + "PID: " + pid + ", TID: " + tid + " FIFO: " + mUseFifoUiScheduling);
}
}
}
@@ -6638,11 +6790,11 @@
Slog.w(TAG, "setHasTopUi called on unknown pid: " + pid);
return;
}
- if (pr.hasTopUi() != hasTopUi) {
+ if (pr.mState.hasTopUi() != hasTopUi) {
if (DEBUG_OOM_ADJ) {
Slog.d(TAG, "Setting hasTopUi=" + hasTopUi + " for pid=" + pid);
}
- pr.setHasTopUi(hasTopUi);
+ pr.mState.setHasTopUi(hasTopUi);
changed = true;
}
}
@@ -6822,42 +6974,46 @@
// manager calls in with its locks held.
boolean killed = false;
- synchronized (mPidsSelfLocked) {
- int worstType = 0;
- for (int i=0; i<pids.length; i++) {
- ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
- if (proc != null) {
- int type = proc.setAdj;
- if (type > worstType) {
- worstType = type;
+ synchronized (this) {
+ synchronized (mProcLock) {
+ synchronized (mPidsSelfLocked) {
+ int worstType = 0;
+ for (int i = 0; i < pids.length; i++) {
+ ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
+ if (proc != null) {
+ int type = proc.mState.getSetAdj();
+ if (type > worstType) {
+ worstType = type;
+ }
+ }
}
- }
- }
- // If the worst oom_adj is somewhere in the cached proc LRU range,
- // then constrain it so we will kill all cached procs.
- if (worstType < ProcessList.CACHED_APP_MAX_ADJ
- && worstType > ProcessList.CACHED_APP_MIN_ADJ) {
- worstType = ProcessList.CACHED_APP_MIN_ADJ;
- }
+ // If the worst oom_adj is somewhere in the cached proc LRU range,
+ // then constrain it so we will kill all cached procs.
+ if (worstType < ProcessList.CACHED_APP_MAX_ADJ
+ && worstType > ProcessList.CACHED_APP_MIN_ADJ) {
+ worstType = ProcessList.CACHED_APP_MIN_ADJ;
+ }
- // If this is not a secure call, don't let it kill processes that
- // are important.
- if (!secure && worstType < ProcessList.SERVICE_ADJ) {
- worstType = ProcessList.SERVICE_ADJ;
- }
+ // If this is not a secure call, don't let it kill processes that
+ // are important.
+ if (!secure && worstType < ProcessList.SERVICE_ADJ) {
+ worstType = ProcessList.SERVICE_ADJ;
+ }
- Slog.w(TAG, "Killing processes " + reason + " at adjustment " + worstType);
- for (int i=0; i<pids.length; i++) {
- ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
- if (proc == null) {
- continue;
- }
- int adj = proc.setAdj;
- if (adj >= worstType && !proc.killedByAm) {
- proc.kill(reason, ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_KILL_PID, true);
- killed = true;
+ Slog.w(TAG, "Killing processes " + reason + " at adjustment " + worstType);
+ for (int i = 0; i < pids.length; i++) {
+ ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
+ if (proc == null) {
+ continue;
+ }
+ int adj = proc.mState.getSetAdj();
+ if (adj >= worstType && !proc.isKilledByAm()) {
+ proc.killLocked(reason, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_KILL_PID, true);
+ killed = true;
+ }
+ }
}
}
}
@@ -6870,13 +7026,15 @@
synchronized (this) {
final long identity = Binder.clearCallingIdentity();
try {
- mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
- ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
- true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
- false /* setRemoved */,
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_KILL_UID,
- reason != null ? reason : "kill uid");
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(null /* packageName */, appId, userId,
+ ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
+ true /* callerWillRestart */, true /* doit */,
+ true /* evenPersistent */, false /* setRemoved */,
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_KILL_UID,
+ reason != null ? reason : "kill uid");
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -6889,13 +7047,15 @@
synchronized (this) {
final long identity = Binder.clearCallingIdentity();
try {
- mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
- ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
- true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
- false /* setRemoved */,
- ApplicationExitInfo.REASON_PERMISSION_CHANGE,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- reason != null ? reason : "kill uid");
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(null /* packageName */, appId, userId,
+ ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
+ true /* callerWillRestart */, true /* doit */,
+ true /* evenPersistent */, false /* setRemoved */,
+ ApplicationExitInfo.REASON_PERMISSION_CHANGE,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ reason != null ? reason : "kill uid");
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -6917,17 +7077,22 @@
}
boolean killed = false;
- synchronized (mPidsSelfLocked) {
- final int size = mPidsSelfLocked.size();
- for (int i = 0; i < size; i++) {
- final int pid = mPidsSelfLocked.keyAt(i);
- final ProcessRecord proc = mPidsSelfLocked.valueAt(i);
- if (proc == null) continue;
+ synchronized (this) {
+ synchronized (mProcLock) {
+ synchronized (mPidsSelfLocked) {
+ final int size = mPidsSelfLocked.size();
+ for (int i = 0; i < size; i++) {
+ final int pid = mPidsSelfLocked.keyAt(i);
+ final ProcessRecord proc = mPidsSelfLocked.valueAt(i);
+ if (proc == null) continue;
- final int adj = proc.setAdj;
- if (adj > belowAdj && !proc.killedByAm) {
- proc.kill(reason, ApplicationExitInfo.REASON_PERMISSION_CHANGE, true);
- killed = true;
+ final int adj = proc.mState.getSetAdj();
+ if (adj > belowAdj && !proc.isKilledByAm()) {
+ proc.killLocked(reason, ApplicationExitInfo.REASON_PERMISSION_CHANGE,
+ true);
+ killed = true;
+ }
+ }
}
}
}
@@ -7033,16 +7198,16 @@
+ android.Manifest.permission.SET_ACTIVITY_WATCHER);
}
- synchronized (this) {
+ synchronized (mProcLock) {
final long now = SystemClock.uptimeMillis();
final long timeSinceLastIdle = now - mLastIdleTime;
// Compact all non-zygote processes to freshen up the page cache.
mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
- final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLocked(now);
+ final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLPr(now);
mLastIdleTime = now;
- mAppProfiler.updateLowRamTimestampLocked(now);
+ mAppProfiler.updateLowRamTimestampLPr(now);
StringBuilder sb = new StringBuilder(128);
sb.append("Idle maintenance over ");
@@ -7059,13 +7224,13 @@
final long totalMemoryInKb = getTotalMemory() / 1000;
final long memoryGrowthThreshold =
Math.max(totalMemoryInKb / 100, MINIMUM_MEMORY_GROWTH_THRESHOLD);
-
- for (int i = mProcessList.mLruProcesses.size() - 1 ; i >= 0 ; i--) {
- ProcessRecord proc = mProcessList.mLruProcesses.get(i);
+ mProcessList.forEachLruProcessesLOSP(false, proc -> {
final ProcessProfileRecord pr = proc.mProfile;
- if (proc.notCachedSinceIdle) {
- if (proc.setProcState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
+ final ProcessStateRecord state = proc.mState;
+ final int setProcState = state.getSetProcState();
+ if (state.isNotCachedSinceIdle()) {
+ if (setProcState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ && setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
final long initialIdlePss, lastPss, lastSwapPss;
synchronized (mAppProfiler.mProfilerLock) {
initialIdlePss = pr.getInitialIdlePss();
@@ -7073,39 +7238,43 @@
lastSwapPss = pr.getLastSwapPss();
}
if (doKilling && initialIdlePss != 0
- && lastPss > ((initialIdlePss * 3) / 2)
+ && lastPss > (initialIdlePss * 3 / 2)
&& lastPss > (initialIdlePss + memoryGrowthThreshold)) {
- sb = new StringBuilder(128);
- sb.append("Kill");
- sb.append(proc.processName);
- sb.append(" in idle maint: pss=");
- sb.append(lastPss);
- sb.append(", swapPss=");
- sb.append(lastSwapPss);
- sb.append(", initialPss=");
- sb.append(initialIdlePss);
- sb.append(", period=");
- TimeUtils.formatDuration(timeSinceLastIdle, sb);
- sb.append(", lowRamPeriod=");
- TimeUtils.formatDuration(lowRamSinceLastIdle, sb);
- Slog.wtfQuiet(TAG, sb.toString());
- proc.kill("idle maint (pss " + lastPss
- + " from " + initialIdlePss + ")",
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE,
- true);
+ final StringBuilder sb2 = new StringBuilder(128);
+ sb2.append("Kill");
+ sb2.append(proc.processName);
+ sb2.append(" in idle maint: pss=");
+ sb2.append(lastPss);
+ sb2.append(", swapPss=");
+ sb2.append(lastSwapPss);
+ sb2.append(", initialPss=");
+ sb2.append(initialIdlePss);
+ sb2.append(", period=");
+ TimeUtils.formatDuration(timeSinceLastIdle, sb2);
+ sb2.append(", lowRamPeriod=");
+ TimeUtils.formatDuration(lowRamSinceLastIdle, sb2);
+ Slog.wtfQuiet(TAG, sb2.toString());
+ mHandler.post(() -> {
+ synchronized (ActivityManagerService.this) {
+ proc.killLocked("idle maint (pss " + lastPss
+ + " from " + initialIdlePss + ")",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE,
+ true);
+ }
+ });
}
}
- } else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME
- && proc.setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) {
- proc.notCachedSinceIdle = true;
+ } else if (setProcState < ActivityManager.PROCESS_STATE_HOME
+ && setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) {
+ state.setNotCachedSinceIdle(true);
synchronized (mAppProfiler.mProfilerLock) {
pr.setInitialIdlePss(0);
mAppProfiler.updateNextPssTimeLPf(
- proc.setProcState, proc.mProfile, now, true);
+ state.getSetProcState(), proc.mProfile, now, true);
}
}
- }
+ });
}
}
@@ -7216,7 +7385,7 @@
synchronized(this) {
if (procsToKill != null) {
- for (int i=procsToKill.size()-1; i>=0; i--) {
+ for (int i = procsToKill.size() - 1; i >= 0; i--) {
ProcessRecord proc = procsToKill.get(i);
Slog.i(TAG, "Removing system update proc: " + proc);
mProcessList.removeProcessLocked(proc, true, false,
@@ -7443,16 +7612,18 @@
private void updateForceBackgroundCheck(boolean enabled) {
synchronized (this) {
- if (mForceBackgroundCheck != enabled) {
- mForceBackgroundCheck = enabled;
+ synchronized (mProcLock) {
+ if (mForceBackgroundCheck != enabled) {
+ mForceBackgroundCheck = enabled;
- if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
- }
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+ }
- if (mForceBackgroundCheck) {
- // Stop background services for idle UIDs.
- mProcessList.doStopUidForIdleUidsLocked();
+ if (mForceBackgroundCheck) {
+ // Stop background services for idle UIDs.
+ mProcessList.doStopUidForIdleUidsLocked();
+ }
}
}
}
@@ -7517,7 +7688,7 @@
(r != null) ? r.uid : -1,
eventType,
processName,
- (r != null) ? r.pid : -1,
+ (r != null) ? r.getPid() : -1,
(r != null && r.info != null) ? r.info.packageName : "",
(r != null && r.info != null) ? (r.info.isInstantApp()
? FrameworkStatsLog.APP_CRASH_OCCURRED__IS_INSTANT_APP__TRUE
@@ -7763,8 +7934,8 @@
return null;
}
- synchronized (this) {
- return mProcessList.findAppProcessLocked(app, reason);
+ synchronized (mProcLock) {
+ return mProcessList.findAppProcessLOSP(app, reason);
}
}
@@ -7773,7 +7944,7 @@
* to append various headers to the dropbox log text.
*/
void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
- StringBuilder sb) {
+ final StringBuilder sb) {
// Watchdog thread ends up invoking this function (with
// a null ProcessRecord) to add the stack file to dropbox.
// Do not acquire a lock on this (am) in such cases, as it
@@ -7787,17 +7958,18 @@
// Note: ProcessRecord 'process' is guarded by the service
// instance. (notably process.pkgList, which could otherwise change
// concurrently during execution of this method)
- synchronized (this) {
+ synchronized (mProcLock) {
sb.append("Process: ").append(processName).append("\n");
- sb.append("PID: ").append(process.pid).append("\n");
+ sb.append("PID: ").append(process.getPid()).append("\n");
sb.append("UID: ").append(process.uid).append("\n");
int flags = process.info.flags;
- IPackageManager pm = AppGlobals.getPackageManager();
+ final IPackageManager pm = AppGlobals.getPackageManager();
sb.append("Flags: 0x").append(Integer.toHexString(flags)).append("\n");
+ final int callingUserId = UserHandle.getCallingUserId();
process.getPkgList().forEachPackage(pkg -> {
sb.append("Package: ").append(pkg);
try {
- PackageInfo pi = pm.getPackageInfo(pkg, 0, UserHandle.getCallingUserId());
+ final PackageInfo pi = pm.getPackageInfo(pkg, 0, callingUserId);
if (pi != null) {
sb.append(" v").append(pi.getLongVersionCode());
if (pi.versionName != null) {
@@ -7816,7 +7988,7 @@
}
private static String processClass(ProcessRecord process) {
- if (process == null || process.pid == MY_PID) {
+ if (process == null || process.getPid() == MY_PID) {
return "system_server";
} else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
return "system_app";
@@ -7878,8 +8050,8 @@
sb.append("Foreground: ")
.append(process.isInterestingToUserLocked() ? "Yes" : "No")
.append("\n");
- if (process.startTime > 0) {
- long runtimeMillis = SystemClock.elapsedRealtime() - process.startTime;
+ if (process.getStartTime() > 0) {
+ long runtimeMillis = SystemClock.elapsedRealtime() - process.getStartTime();
sb.append("Process-Runtime: ").append(runtimeMillis).append("\n");
}
}
@@ -7887,7 +8059,7 @@
sb.append("Activity: ").append(activityShortComponentName).append("\n");
}
if (parentShortComponentName != null) {
- if (parentProcess != null && parentProcess.pid != process.pid) {
+ if (parentProcess != null && parentProcess.getPid() != process.getPid()) {
sb.append("Parent-Process: ").append(parentProcess.processName).append("\n");
}
if (!parentShortComponentName.equals(activityShortComponentName)) {
@@ -7983,47 +8155,46 @@
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
enforceNotIsolatedCaller("getProcessesInErrorState");
// assume our apps are happy - lazy create the list
- List<ActivityManager.ProcessErrorStateInfo> errList = null;
+ final List<ActivityManager.ProcessErrorStateInfo>[] errList = new List[1];
final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL,
Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
int userId = UserHandle.getUserId(Binder.getCallingUid());
- synchronized (this) {
-
+ synchronized (mProcLock) {
// iterate across all processes
- for (int i=mProcessList.mLruProcesses.size()-1; i>=0; i--) {
- ProcessRecord app = mProcessList.mLruProcesses.get(i);
+ mProcessList.forEachLruProcessesLOSP(false, app -> {
if (!allUsers && app.userId != userId) {
- continue;
+ return;
}
- final boolean crashing = app.isCrashing();
- final boolean notResponding = app.isNotResponding();
- if ((app.thread != null) && (crashing || notResponding)) {
+ final ProcessErrorStateRecord errState = app.mErrorState;
+ final boolean crashing = errState.isCrashing();
+ final boolean notResponding = errState.isNotResponding();
+ if ((app.getThread() != null) && (crashing || notResponding)) {
// This one's in trouble, so we'll generate a report for it
// crashes are higher priority (in case there's a crash *and* an anr)
ActivityManager.ProcessErrorStateInfo report = null;
if (crashing) {
- report = app.crashingReport;
+ report = errState.getCrashingReport();
} else if (notResponding) {
- report = app.notRespondingReport;
+ report = errState.getNotRespondingReport();
}
if (report != null) {
- if (errList == null) {
- errList = new ArrayList<>(1);
+ if (errList[0] == null) {
+ errList[0] = new ArrayList<>(1);
}
- errList.add(report);
+ errList[0].add(report);
} else {
Slog.w(TAG, "Missing app error report, app = " + app.processName +
" crashing = " + crashing +
" notResponding = " + notResponding);
}
}
- }
+ });
}
- return errList;
+ return errList[0];
}
@Override
@@ -8039,9 +8210,9 @@
final boolean allUids = mAtmInternal.isGetTasksAllowed(
"getRunningAppProcesses", Binder.getCallingPid(), callingUid);
- synchronized (this) {
+ synchronized (mProcLock) {
// Iterate across all processes
- return mProcessList.getRunningAppProcessesLocked(allUsers, userId, allUids,
+ return mProcessList.getRunningAppProcessesLOSP(allUsers, userId, allUids,
callingUid, clientTargetSdk);
}
}
@@ -8152,13 +8323,13 @@
final int callingUid = Binder.getCallingUid();
final int clientTargetSdk = mPackageManagerInt.getUidTargetSdkVersion(callingUid);
- synchronized (this) {
+ synchronized (mProcLock) {
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(Binder.getCallingPid());
}
if (proc != null) {
- mProcessList.fillInProcMemInfoLocked(proc, outState, clientTargetSdk);
+ mProcessList.fillInProcMemInfoLOSP(proc, outState, clientTargetSdk);
}
}
}
@@ -8204,7 +8375,9 @@
synchronized(this) {
mConstants.dump(pw);
- mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
+ synchronized (mProcLock) {
+ mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
+ }
mOomAdjuster.dumpCacheOomRankerSettings(pw);
pw.println();
if (dumpAll) {
@@ -8336,7 +8509,9 @@
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- mProcessList.dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
+ synchronized (mProcLock) {
+ mProcessList.dumpProcessesLSP(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
+ }
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -8440,7 +8615,9 @@
}
// output proto is ProcessProto
synchronized (this) {
- mProcessList.writeProcessesToProtoLocked(proto, dumpPackage);
+ synchronized (mProcLock) {
+ mProcessList.writeProcessesToProtoLSP(proto, dumpPackage);
+ }
}
} else {
// default option, dump everything, output is ActivityManagerServiceProto
@@ -8459,7 +8636,9 @@
proto.end(serviceToken);
long processToken = proto.start(ActivityManagerServiceProto.PROCESSES);
- mProcessList.writeProcessesToProtoLocked(proto, dumpPackage);
+ synchronized (mProcLock) {
+ mProcessList.writeProcessesToProtoLSP(proto, dumpPackage);
+ }
proto.end(processToken);
}
}
@@ -8533,8 +8712,10 @@
opti++;
}
synchronized (this) {
- mProcessList.dumpProcessesLocked(
- fd, pw, args, opti, true, dumpPackage, dumpAppId);
+ synchronized (mProcLock) {
+ mProcessList.dumpProcessesLSP(
+ fd, pw, args, opti, true, dumpPackage, dumpAppId);
+ }
}
} else if ("oom".equals(cmd) || "o".equals(cmd)) {
synchronized (this) {
@@ -8622,6 +8803,8 @@
} else if ("settings".equals(cmd)) {
synchronized (this) {
mConstants.dump(pw);
+ }
+ synchronized (mProcLock) {
mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
mOomAdjuster.dumpCacheOomRankerSettings(pw);
}
@@ -8871,8 +9054,8 @@
return needSep;
}
- @GuardedBy("this")
- void dumpOtherProcessesInfoLocked(FileDescriptor fd, PrintWriter pw,
+ @GuardedBy({"this", "mProcLock"})
+ void dumpOtherProcessesInfoLSP(FileDescriptor fd, PrintWriter pw,
boolean dumpAll, String dumpPackage, int dumpAppId, int numPers, boolean needSep) {
if (dumpAll || dumpPackage != null) {
final SparseArray<ProcessRecord> pidToProcess = new SparseArray<>();
@@ -8880,7 +9063,7 @@
boolean printed = false;
for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
ProcessRecord r = mPidsSelfLocked.valueAt(i);
- pidToProcess.put(r.pid, r);
+ pidToProcess.put(r.getPid(), r);
if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
continue;
}
@@ -8891,7 +9074,7 @@
printed = true;
}
pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i));
- pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
+ pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
}
}
@@ -8931,8 +9114,7 @@
synchronized (mPidsSelfLocked) {
boolean printed = false;
for (int i = 0, size = mImportantProcesses.size(); i < size; i++) {
- ProcessRecord r = mPidsSelfLocked.get(
- mImportantProcesses.valueAt(i).pid);
+ ProcessRecord r = mPidsSelfLocked.get(mImportantProcesses.valueAt(i).pid);
if (dumpPackage != null && (r == null
|| !r.getPkgList().containsKey(dumpPackage))) {
continue;
@@ -8973,7 +9155,7 @@
"OnHold Norm", "OnHold PERS", dumpPackage);
}
- needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
+ needSep = mAppErrors.dumpLPr(fd, pw, needSep, dumpPackage);
needSep = mAtmInternal.dumpForProcesses(fd, pw, dumpAll, dumpPackage, dumpAppId, needSep,
mAppProfiler.getTestPssMode(), mWakefulness.get());
@@ -9068,7 +9250,7 @@
TimeUtils.formatDuration(now, mLastIdleTime, pw);
pw.print(" mLowRamSinceLastIdle=");
TimeUtils.formatDuration(
- mAppProfiler.getLowRamTimeSinceIdleLocked(now), pw);
+ mAppProfiler.getLowRamTimeSinceIdleLPr(now), pw);
pw.println();
pw.println();
@@ -9085,8 +9267,8 @@
mUserController.dump(pw);
}
- @GuardedBy("this")
- void writeOtherProcessesInfoToProtoLocked(ProtoOutputStream proto, String dumpPackage,
+ @GuardedBy({"this", "mProcLock"})
+ void writeOtherProcessesInfoToProtoLSP(ProtoOutputStream proto, String dumpPackage,
int dumpAppId, int numPers) {
for (int i = 0, size = mActiveInstrumentation.size(); i < size; i++) {
ActiveInstrumentation ai = mActiveInstrumentation.get(i);
@@ -9103,7 +9285,7 @@
if (dumpPackage != null) {
synchronized (mPidsSelfLocked) {
- for (int i=0; i<mPidsSelfLocked.size(); i++) {
+ for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
ProcessRecord r = mPidsSelfLocked.valueAt(i);
if (!r.getPkgList().containsKey(dumpPackage)) {
continue;
@@ -9159,7 +9341,7 @@
ActivityManagerServiceDumpProcessesProto.GC_PROCS,
dumpPackage);
}
- mAppErrors.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS,
+ mAppErrors.dumpDebugLPr(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS,
dumpPackage);
mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness.get(),
mAppProfiler.getTestPssMode());
@@ -9232,7 +9414,7 @@
long now = SystemClock.uptimeMillis();
ProtoUtils.toDuration(proto, ActivityManagerServiceDumpProcessesProto.LAST_IDLE_TIME, mLastIdleTime, now);
proto.write(ActivityManagerServiceDumpProcessesProto.LOW_RAM_SINCE_LAST_IDLE_MS,
- mAppProfiler.getLowRamTimeSinceIdleLocked(now));
+ mAppProfiler.getLowRamTimeSinceIdleLPr(now));
}
}
@@ -9579,14 +9761,13 @@
mUgmInternal.dump(pw, dumpAll, dumpPackage);
}
- private static final int dumpProcessList(PrintWriter pw,
+ private static int dumpProcessList(PrintWriter pw,
ActivityManagerService service, List list,
String prefix, String normalLabel, String persistentLabel,
String dumpPackage) {
int numPers = 0;
- final int N = list.size()-1;
- for (int i=N; i>=0; i--) {
- ProcessRecord r = (ProcessRecord)list.get(i);
+ for (int i = list.size() - 1; i >= 0; i--) {
+ ProcessRecord r = (ProcessRecord) list.get(i);
if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
continue;
}
@@ -9602,8 +9783,8 @@
ArrayList<ProcessRecord> collectProcesses(PrintWriter pw, int start, boolean allPkgs,
String[] args) {
- synchronized (this) {
- return mProcessList.collectProcessesLocked(start, allPkgs, args);
+ synchronized (mProcLock) {
+ return mProcessList.collectProcessesLOSP(start, allPkgs, args);
}
}
@@ -9622,13 +9803,15 @@
for (int i = procs.size() - 1 ; i >= 0 ; i--) {
ProcessRecord r = procs.get(i);
- if (r.thread != null) {
- pw.println("\n** Graphics info for pid " + r.pid + " [" + r.processName + "] **");
+ final int pid = r.getPid();
+ final IApplicationThread thread = r.getThread();
+ if (thread != null) {
+ pw.println("\n** Graphics info for pid " + pid + " [" + r.processName + "] **");
pw.flush();
try {
TransferPipe tp = new TransferPipe();
try {
- r.thread.dumpGfxInfo(tp.getWriteFd(), args);
+ thread.dumpGfxInfo(tp.getWriteFd(), args);
tp.go(fd);
} finally {
tp.kill();
@@ -9655,13 +9838,15 @@
for (int i = procs.size() - 1; i >= 0; i--) {
ProcessRecord r = procs.get(i);
- if (r.thread != null) {
- pw.println("\n\n** Cache info for pid " + r.pid + " [" + r.processName + "] **");
+ final int pid = r.getPid();
+ final IApplicationThread thread = r.getThread();
+ if (thread != null) {
+ pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **");
pw.flush();
try {
TransferPipe tp = new TransferPipe();
try {
- r.thread.dumpCacheInfo(tp.getWriteFd(), args);
+ thread.dumpCacheInfo(tp.getWriteFd(), args);
tp.go(fd);
} finally {
tp.kill();
@@ -9688,13 +9873,15 @@
for (int i = procs.size() - 1 ; i >= 0 ; i--) {
ProcessRecord r = procs.get(i);
- if (r.thread != null) {
- pw.println("\n** Database info for pid " + r.pid + " [" + r.processName + "] **");
+ final int pid = r.getPid();
+ final IApplicationThread thread = r.getThread();
+ if (thread != null) {
+ pw.println("\n** Database info for pid " + pid + " [" + r.processName + "] **");
pw.flush();
try {
TransferPipe tp = new TransferPipe();
try {
- r.thread.dumpDbInfo(tp.getWriteFd(), args);
+ thread.dumpDbInfo(tp.getWriteFd(), args);
tp.go(fd);
} finally {
tp.kill();
@@ -10157,10 +10344,10 @@
final int pid;
final int oomAdj;
final boolean hasActivities;
- synchronized (this) {
- thread = r.thread;
- pid = r.pid;
- oomAdj = r.getSetAdjWithServices();
+ synchronized (mProcLock) {
+ thread = r.getThread();
+ pid = r.getPid();
+ oomAdj = r.mState.getSetAdjWithServices();
hasActivities = r.hasActivities();
}
if (thread != null) {
@@ -10231,8 +10418,8 @@
final long myTotalRss = mi.getTotalRss();
final long myTotalSwapPss = mi.getTotalSwappedOutPss();
- synchronized (this) {
- if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
+ synchronized (mProcLock) {
+ if (r.getThread() != null && oomAdj == r.mState.getSetAdjWithServices()) {
// Record this for posterity if the process has been stable.
r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
reportType, endTime - startTime);
@@ -10742,10 +10929,10 @@
final int pid;
final int oomAdj;
final boolean hasActivities;
- synchronized (this) {
- thread = r.thread;
- pid = r.pid;
- oomAdj = r.getSetAdjWithServices();
+ synchronized (mProcLock) {
+ thread = r.getThread();
+ pid = r.getPid();
+ oomAdj = r.mState.getSetAdjWithServices();
hasActivities = r.hasActivities();
}
if (thread == null) {
@@ -10811,8 +10998,8 @@
final long myTotalRss = mi.getTotalRss();
final long myTotalSwapPss = mi.getTotalSwappedOutPss();
- synchronized (this) {
- if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
+ synchronized (mProcLock) {
+ if (r.getThread() != null && oomAdj == r.mState.getSetAdjWithServices()) {
// Record this for posterity if the process has been stable.
r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
reportType, endTime - startTime);
@@ -11151,96 +11338,26 @@
* app that was passed in must remain on the process lists.
*/
@GuardedBy("this")
- final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
+ final boolean cleanUpApplicationRecordLocked(ProcessRecord app, int pid,
boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
- if (index >= 0) {
- removeLruProcessLocked(app);
- ProcessList.remove(app.pid);
- }
+ boolean restart;
+ synchronized (mProcLock) {
+ if (index >= 0) {
+ removeLruProcessLocked(app);
+ ProcessList.remove(pid);
+ }
+ restart = app.onCleanupApplicationRecordLSP(mProcessStats, allowRestart);
+ }
mAppProfiler.onCleanupApplicationRecordLocked(app);
-
- // Dismiss any open dialogs.
- app.getDialogController().clearAllErrorDialogs();
-
- app.setCrashing(false);
- app.setNotResponding(false);
-
- app.resetPackageList(mProcessStats);
- app.unlinkDeathRecipient();
- app.makeInactive(mProcessStats);
- app.waitingToKill = null;
- app.forcingToImportant = null;
- updateProcessForegroundLocked(app, false, 0, false);
- app.setHasForegroundActivities(false);
- app.hasShownUi = false;
- app.treatLikeActivity = false;
- app.hasAboveClient = false;
- app.setHasClientActivities(false);
-
- mServices.killServicesLocked(app, allowRestart);
- mPhantomProcessList.onAppDied(app.pid);
-
- boolean restart = false;
-
- // Remove published content providers.
- for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
- ContentProviderRecord cpr = app.pubProviders.valueAt(i);
- if (cpr.proc != app) {
- // If the hosting process record isn't really us, bail out
- continue;
- }
- final boolean alwaysRemove = app.bad || !allowRestart;
- final boolean inLaunching = mCpHelper.removeDyingProviderLocked(app, cpr, alwaysRemove);
- if (!alwaysRemove && inLaunching && cpr.hasConnectionOrHandle()) {
- // We left the provider in the launching list, need to
- // restart it.
- restart = true;
- }
-
- cpr.provider = null;
- cpr.setProcess(null);
- }
- app.pubProviders.clear();
-
- // Take care of any launching providers waiting for this process.
- if (mCpHelper.cleanupAppInLaunchingProvidersLocked(app, false)) {
- mProcessList.noteProcessDiedLocked(app);
- restart = true;
- }
-
- // Unregister from connected content providers.
- if (!app.conProviders.isEmpty()) {
- for (int i = app.conProviders.size() - 1; i >= 0; i--) {
- ContentProviderConnection conn = app.conProviders.get(i);
- conn.provider.connections.remove(conn);
- stopAssociationLocked(app.uid, app.processName, conn.provider.uid,
- conn.provider.appInfo.longVersionCode, conn.provider.name,
- conn.provider.info.processName);
- }
- app.conProviders.clear();
- }
-
- // At this point there may be remaining entries in mLaunchingProviders
- // where we were the only one waiting, so they are no longer of use.
- // Look for these and clean up if found.
- // XXX Commented out for now. Trying to figure out a way to reproduce
- // the actual situation to identify what is actually going on.
- if (false) {
- mCpHelper.cleanupLaunchingProvidersLocked();
- }
-
skipCurrentReceiverLocked(app);
-
- // Unregister any receivers.
- for (int i = app.receivers.size() - 1; i >= 0; i--) {
- removeReceiverLocked(app.receivers.valueAt(i));
- }
- app.receivers.clear();
+ updateProcessForegroundLocked(app, false, 0, false);
+ mServices.killServicesLocked(app, allowRestart);
+ mPhantomProcessList.onAppDied(pid);
// If the app is undergoing backup, tell the backup manager about it
final BackupRecord backupTarget = mBackupTargets.get(app.userId);
- if (backupTarget != null && app.pid == backupTarget.app.pid) {
+ if (backupTarget != null && pid == backupTarget.app.getPid()) {
if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
+ backupTarget.appInfo + " died during backup");
mHandler.post(new Runnable() {
@@ -11257,9 +11374,9 @@
});
}
- mProcessList.scheduleDispatchProcessDiedLocked(app.pid, app.info.uid);
+ mProcessList.scheduleDispatchProcessDiedLocked(pid, app.info.uid);
- // If this is a precede instance of another process instance
+ // If this is a preceding instance of another process instance
allowRestart = true;
synchronized (app) {
if (app.mSuccessor != null) {
@@ -11267,13 +11384,13 @@
// because we have created a new one already.
allowRestart = false;
// If it's persistent, add the successor to mPersistentStartingProcesses
- if (app.isPersistent() && !app.removed) {
+ if (app.isPersistent() && !app.isRemoved()) {
if (mPersistentStartingProcesses.indexOf(app.mSuccessor) < 0) {
mPersistentStartingProcesses.add(app.mSuccessor);
}
}
// clean up the field so the successor's proc starter could proceed.
- app.mSuccessor.mPrecedence = null;
+ app.mSuccessor.mPredecessor = null;
app.mSuccessor = null;
// Notify if anyone is waiting for it.
app.notifyAll();
@@ -11293,7 +11410,7 @@
mProcessList.removeProcessNameLocked(app.processName, app.uid, app);
}
mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
- } else if (!app.removed) {
+ } else if (!app.isRemoved()) {
// This app is persistent, so we need to keep its record around.
// If it is not already on the pending app list, add it there
// and start a new process for it.
@@ -11313,7 +11430,7 @@
// We have components that still need to be running in the
// process, so re-launch it.
if (index < 0) {
- ProcessList.remove(app.pid);
+ ProcessList.remove(pid);
}
// Remove provider publish timeout because we will start a new timeout when the
@@ -11321,14 +11438,13 @@
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, app);
mProcessList.addProcessNameLocked(app);
- app.pendingStart = false;
- mProcessList.startProcessLocked(app,
- new HostingRecord("restart", app.processName),
+ app.setPendingStart(false);
+ mProcessList.startProcessLocked(app, new HostingRecord("restart", app.processName),
ZYGOTE_POLICY_FLAG_EMPTY);
return true;
- } else if (app.pid > 0 && app.pid != MY_PID) {
+ } else if (pid > 0 && pid != MY_PID) {
// Goodbye!
- removePidLocked(app);
+ removePidLocked(pid, app);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
if (app.isolated) {
@@ -11667,12 +11783,12 @@
// process after the full backup is done and the ProcessRecord will vaporize anyway.
if (UserHandle.isApp(app.uid) &&
backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) {
- proc.inFullBackup = true;
+ proc.setInFullBackup(true);
}
r.app = proc;
final BackupRecord backupTarget = mBackupTargets.get(targetUserId);
oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1;
- newBackupUid = proc.inFullBackup ? r.appInfo.uid : -1;
+ newBackupUid = proc.isInFullBackup() ? r.appInfo.uid : -1;
mBackupTargets.put(targetUserId, r);
// Try not to kill the process during backup
@@ -11680,10 +11796,11 @@
// If the process is already attached, schedule the creation of the backup agent now.
// If it is not yet live, this will be done when it attaches to the framework.
- if (proc.thread != null) {
+ final IApplicationThread thread = proc.getThread();
+ if (thread != null) {
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
try {
- proc.thread.scheduleCreateBackupAgent(app,
+ thread.scheduleCreateBackupAgent(app,
compatibilityInfoForPackage(app), backupMode, targetUserId,
operationType);
} catch (RemoteException e) {
@@ -11793,14 +11910,15 @@
// Not backing this app up any more; reset its OOM adjustment
final ProcessRecord proc = backupTarget.app;
updateOomAdjLocked(proc, true, OomAdjuster.OOM_ADJ_REASON_NONE);
- proc.inFullBackup = false;
+ proc.setInFullBackup(false);
oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1;
// If the app crashed during backup, 'thread' will be null here
- if (proc.thread != null) {
+ final IApplicationThread thread = proc.getThread();
+ if (thread != null) {
try {
- proc.thread.scheduleDestroyBackupAgent(appInfo,
+ thread.scheduleDestroyBackupAgent(appInfo,
compatibilityInfoForPackage(appInfo), userId);
} catch (Exception e) {
Slog.e(TAG, "Exception when unbinding backup agent:");
@@ -11895,7 +12013,7 @@
boolean instantApp;
synchronized(this) {
if (caller != null) {
- callerApp = getRecordForAppLocked(caller);
+ callerApp = getRecordForAppLOSP(caller);
if (callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
@@ -11909,7 +12027,7 @@
+ " is not running in process " + callerApp);
}
callingUid = callerApp.info.uid;
- callingPid = callerApp.pid;
+ callingPid = callerApp.getPid();
} else {
callerPackage = null;
callingUid = Binder.getCallingUid();
@@ -11978,8 +12096,9 @@
}
synchronized (this) {
- if (callerApp != null && (callerApp.thread == null
- || callerApp.thread.asBinder() != caller.asBinder())) {
+ IApplicationThread thread;
+ if (callerApp != null && ((thread = callerApp.getThread()) == null
+ || thread.asBinder() != caller.asBinder())) {
// Original caller already died
return null;
}
@@ -11988,13 +12107,13 @@
rl = new ReceiverList(this, callerApp, callingPid, callingUid,
userId, receiver);
if (rl.app != null) {
- final int totalReceiversForApp = rl.app.receivers.size();
+ final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
throw new IllegalStateException("Too many receivers, total of "
+ totalReceiversForApp + ", registered for pid: "
+ rl.pid + ", callerPackage: " + callerPackage);
}
- rl.app.receivers.add(rl);
+ rl.app.mReceivers.addReceiver(rl);
} else {
try {
receiver.asBinder().linkToDeath(rl, 0);
@@ -12080,7 +12199,7 @@
}
if (rl.app != null) {
- rl.app.receivers.remove(rl);
+ rl.app.mReceivers.removeReceiver(rl);
}
removeReceiverLocked(rl);
if (rl.linkedToDeath) {
@@ -12379,7 +12498,7 @@
}
}
if (brOptions.isDontSendToRestrictedApps()
- && !isUidActiveLocked(callingUid)
+ && !isUidActiveLOSP(callingUid)
&& isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
+ " has background restrictions");
@@ -12586,12 +12705,14 @@
if (killProcess) {
final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
-1);
- mProcessList.killPackageProcessesLocked(ssp,
- UserHandle.getAppId(extraUid),
- userId, ProcessList.INVALID_ADJ,
- ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- "change " + ssp);
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(ssp,
+ UserHandle.getAppId(extraUid),
+ userId, ProcessList.INVALID_ADJ,
+ ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "change " + ssp);
+ }
}
cleanupDisabledPackageComponentsLocked(ssp, userId,
intent.getStringArrayExtra(
@@ -12735,7 +12856,7 @@
Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
final int uid = getUidFromIntent(intent);
if (uid != -1) {
- final UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
+ final UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
if (uidRec != null) {
uidRec.updateHasInternetPermission();
}
@@ -13120,7 +13241,7 @@
synchronized(this) {
intent = verifyBroadcastLocked(intent);
- final ProcessRecord callerApp = getRecordForAppLocked(caller);
+ final ProcessRecord callerApp = getRecordForAppLOSP(caller);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
@@ -13349,38 +13470,40 @@
|| (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
boolean disableTestApiChecks = disableHiddenApiChecks
|| (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) != 0;
+
if (disableHiddenApiChecks || disableTestApiChecks) {
enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS,
"disable hidden API checks");
- enableTestApiAccess(ii.packageName);
+ enableTestApiAccess(ai.packageName);
}
final long origId = Binder.clearCallingIdentity();
ProcessRecord app;
- if (noRestart) {
- app = getProcessRecordLocked(ai.processName, ai.uid, true);
- } else {
- // Instrumentation can kill and relaunch even persistent processes
- forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
- "start instr");
- // Inform usage stats to make the target package active
- if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(ii.targetPackage, userId,
- UsageEvents.Event.SYSTEM_INTERACTION);
+ synchronized (mProcLock) {
+ if (noRestart) {
+ app = getProcessRecordLocked(ai.processName, ai.uid, true);
+ } else {
+ // Instrumentation can kill and relaunch even persistent processes
+ forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false,
+ userId, "start instr");
+ // Inform usage stats to make the target package active
+ if (mUsageStatsService != null) {
+ mUsageStatsService.reportEvent(ii.targetPackage, userId,
+ UsageEvents.Event.SYSTEM_INTERACTION);
+ }
+ app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks, abiOverride,
+ ZYGOTE_POLICY_FLAG_EMPTY);
}
- app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks,
- disableTestApiChecks, abiOverride, ZYGOTE_POLICY_FLAG_EMPTY);
- }
-
- app.setActiveInstrumentation(activeInstr);
- activeInstr.mFinished = false;
- activeInstr.mSourceUid = callingUid;
- activeInstr.mRunningProcesses.add(app);
- if (!mActiveInstrumentation.contains(activeInstr)) {
- mActiveInstrumentation.add(activeInstr);
+ app.setActiveInstrumentation(activeInstr);
+ activeInstr.mFinished = false;
+ activeInstr.mSourceUid = callingUid;
+ activeInstr.mRunningProcesses.add(app);
+ if (!mActiveInstrumentation.contains(activeInstr)) {
+ mActiveInstrumentation.add(activeInstr);
+ }
}
if ((flags & INSTR_FLAG_DISABLE_ISOLATED_STORAGE) != 0) {
@@ -13407,7 +13530,7 @@
}
try {
- pr.thread.instrumentWithoutRestart(
+ pr.getThread().instrumentWithoutRestart(
activeInstr.mClass,
activeInstr.mArguments,
activeInstr.mWatcher,
@@ -13467,7 +13590,7 @@
}
synchronized(this) {
- ProcessRecord app = getRecordForAppLocked(target);
+ ProcessRecord app = getRecordForAppLOSP(target);
if (app == null) {
Slog.w(TAG, "addInstrumentationResults: no app for " + target);
return;
@@ -13489,36 +13612,38 @@
return;
}
- if (!instr.mFinished) {
- if (instr.mWatcher != null) {
- Bundle finalResults = instr.mCurResults;
- if (finalResults != null) {
- if (instr.mCurResults != null && results != null) {
- finalResults.putAll(results);
+ synchronized (mProcLock) {
+ if (!instr.mFinished) {
+ if (instr.mWatcher != null) {
+ Bundle finalResults = instr.mCurResults;
+ if (finalResults != null) {
+ if (instr.mCurResults != null && results != null) {
+ finalResults.putAll(results);
+ }
+ } else {
+ finalResults = results;
}
- } else {
- finalResults = results;
+ mInstrumentationReporter.reportFinished(instr.mWatcher,
+ instr.mClass, resultCode, finalResults);
}
- mInstrumentationReporter.reportFinished(instr.mWatcher,
- instr.mClass, resultCode, finalResults);
+
+ // Can't call out of the system process with a lock held, so post a message.
+ if (instr.mUiAutomationConnection != null) {
+ // Go back to the default mode of denying OP_NO_ISOLATED_STORAGE app op.
+ mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, app.uid,
+ app.info.packageName, AppOpsManager.MODE_ERRORED);
+ mAppOpsService.setAppOpsServiceDelegate(null);
+ getPermissionManagerInternal().stopShellPermissionIdentityDelegation();
+ mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG,
+ instr.mUiAutomationConnection).sendToTarget();
+ }
+ instr.mFinished = true;
}
- // Can't call out of the system process with a lock held, so post a message.
- if (instr.mUiAutomationConnection != null) {
- // Go back to the default mode of denying OP_NO_ISOLATED_STORAGE app op.
- mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, app.uid,
- app.info.packageName, AppOpsManager.MODE_ERRORED);
- mAppOpsService.setAppOpsServiceDelegate(null);
- getPermissionManagerInternal().stopShellPermissionIdentityDelegation();
- mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG,
- instr.mUiAutomationConnection).sendToTarget();
- }
- instr.mFinished = true;
+ instr.removeProcess(app);
+ app.setActiveInstrumentation(null);
}
- instr.removeProcess(app);
- app.setActiveInstrumentation(null);
-
if (!instr.mNoRestart) {
forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false,
app.userId,
@@ -13554,7 +13679,7 @@
}
synchronized(this) {
- ProcessRecord app = getRecordForAppLocked(target);
+ ProcessRecord app = getRecordForAppLOSP(target);
if (app == null) {
Slog.w(TAG, "finishInstrumentation: no app for " + target);
return;
@@ -13660,10 +13785,11 @@
// the current [or imminent] receiver on.
boolean isReceivingBroadcastLocked(ProcessRecord app,
ArraySet<BroadcastQueue> receivingQueues) {
- final int N = app.curReceivers.size();
- if (N > 0) {
- for (int i = 0; i < N; i++) {
- receivingQueues.add(app.curReceivers.valueAt(i).queue);
+ final ProcessReceiverRecord prr = app.mReceivers;
+ final int numOfReceivers = prr.numberOfCurReceivers();
+ if (numOfReceivers > 0) {
+ for (int i = 0; i < numOfReceivers; i++) {
+ receivingQueues.add(prr.getCurReceiverAt(i).queue);
}
return true;
}
@@ -13782,6 +13908,7 @@
/**
* Returns true if things are idle enough to perform GCs.
*/
+ @GuardedBy("this")
final boolean canGcNowLocked() {
for (BroadcastQueue q : mBroadcastQueues) {
if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isEmpty()) {
@@ -13794,77 +13921,91 @@
private void checkExcessivePowerUsage() {
updateCpuStatsNow();
- synchronized (this) {
- boolean doCpuKills = true;
- if (mLastPowerCheckUptime == 0) {
- doCpuKills = false;
- }
+ synchronized (mProcLock) {
+ final boolean doCpuKills = mLastPowerCheckUptime != 0;
final long curUptime = SystemClock.uptimeMillis();
final long uptimeSince = curUptime - mLastPowerCheckUptime;
mLastPowerCheckUptime = curUptime;
- int i = mProcessList.mLruProcesses.size();
- while (i > 0) {
- i--;
- final ProcessRecord app = mProcessList.mLruProcesses.get(i);
- if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
+ mProcessList.forEachLruProcessesLOSP(false, app -> {
+ if (app.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_HOME) {
int cpuLimit;
- long checkDur = curUptime - app.getWhenUnimportant();
+ long checkDur = curUptime - app.mState.getWhenUnimportant();
if (checkDur <= mConstants.POWER_CHECK_INTERVAL) {
cpuLimit = mConstants.POWER_CHECK_MAX_CPU_1;
} else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 2)
- || app.setProcState <= ActivityManager.PROCESS_STATE_HOME) {
+ || app.mState.getSetProcState() <= ActivityManager.PROCESS_STATE_HOME) {
cpuLimit = mConstants.POWER_CHECK_MAX_CPU_2;
} else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 3)) {
cpuLimit = mConstants.POWER_CHECK_MAX_CPU_3;
} else {
cpuLimit = mConstants.POWER_CHECK_MAX_CPU_4;
}
- synchronized (mAppProfiler.mProfilerLock) {
- final ProcessProfileRecord profile = app.mProfile;
- final long curCpuTime = profile.mCurCpuTime.get();
- final long lastCpuTime = profile.mLastCpuTime.get();
- if (lastCpuTime > 0) {
- final long cputimeUsed = curCpuTime - lastCpuTime;
- if (checkExcessivePowerUsageLocked(uptimeSince, doCpuKills, cputimeUsed,
- app.processName, app.toShortString(), cpuLimit, app)) {
- app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
- + " dur=" + checkDur + " limit=" + cpuLimit,
- ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
- ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
- true);
- profile.reportExcessiveCpu();
- }
- }
- profile.mLastCpuTime.set(curCpuTime);
- }
+ updateAppProcessCpuTimeLPr(uptimeSince, doCpuKills, checkDur, cpuLimit, app);
// Also check the phantom processes if there is any
- final long chkDur = checkDur;
- final int cpuLmt = cpuLimit;
- final boolean doKill = doCpuKills;
- mPhantomProcessList.forEachPhantomProcessOfApp(app, r -> {
- if (r.mLastCputime > 0) {
- final long cputimeUsed = r.mCurrentCputime - r.mLastCputime;
- if (checkExcessivePowerUsageLocked(uptimeSince, doKill, cputimeUsed,
- app.processName, r.toString(), cpuLimit, app)) {
- mPhantomProcessList.killPhantomProcessGroupLocked(app, r,
- ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
- ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
- "excessive cpu " + cputimeUsed + " during "
- + uptimeSince + " dur=" + chkDur + " limit=" + cpuLmt);
- return false;
- }
- }
- r.mLastCputime = r.mCurrentCputime;
- return true;
- });
+ updatePhantomProcessCpuTimeLPr(
+ uptimeSince, doCpuKills, checkDur, cpuLimit, app);
}
- }
+ });
}
}
- private boolean checkExcessivePowerUsageLocked(final long uptimeSince, boolean doCpuKills,
+ @GuardedBy("mProcLock")
+ private void updateAppProcessCpuTimeLPr(final long uptimeSince, final boolean doCpuKills,
+ final long checkDur, final int cpuLimit, final ProcessRecord app) {
+ synchronized (mAppProfiler.mProfilerLock) {
+ final ProcessProfileRecord profile = app.mProfile;
+ final long curCpuTime = profile.mCurCpuTime.get();
+ final long lastCpuTime = profile.mLastCpuTime.get();
+ if (lastCpuTime > 0) {
+ final long cpuTimeUsed = curCpuTime - lastCpuTime;
+ if (checkExcessivePowerUsageLPr(uptimeSince, doCpuKills, cpuTimeUsed,
+ app.processName, app.toShortString(), cpuLimit, app)) {
+ mHandler.post(() -> {
+ synchronized (ActivityManagerService.this) {
+ app.killLocked("excessive cpu " + cpuTimeUsed + " during "
+ + uptimeSince + " dur=" + checkDur + " limit=" + cpuLimit,
+ ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+ ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+ true);
+ }
+ });
+ profile.reportExcessiveCpu();
+ }
+ }
+
+ profile.mLastCpuTime.set(curCpuTime);
+ }
+ }
+
+ @GuardedBy("mProcLock")
+ private void updatePhantomProcessCpuTimeLPr(final long uptimeSince, final boolean doCpuKills,
+ final long checkDur, final int cpuLimit, final ProcessRecord app) {
+ mPhantomProcessList.forEachPhantomProcessOfApp(app, r -> {
+ if (r.mLastCputime > 0) {
+ final long cpuTimeUsed = r.mCurrentCputime - r.mLastCputime;
+ if (checkExcessivePowerUsageLPr(uptimeSince, doCpuKills, cpuTimeUsed,
+ app.processName, r.toString(), cpuLimit, app)) {
+ mHandler.post(() -> {
+ synchronized (ActivityManagerService.this) {
+ mPhantomProcessList.killPhantomProcessGroupLocked(app, r,
+ ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+ ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+ "excessive cpu " + cpuTimeUsed + " during "
+ + uptimeSince + " dur=" + checkDur + " limit=" + cpuLimit);
+ }
+ });
+ return false;
+ }
+ }
+ r.mLastCputime = r.mCurrentCputime;
+ return true;
+ });
+ }
+
+ @GuardedBy("mProcLock")
+ private boolean checkExcessivePowerUsageLPr(final long uptimeSince, boolean doCpuKills,
final long cputimeUsed, final String processName, final String description,
final int cpuLimit, final ProcessRecord app) {
if (DEBUG_POWER) {
@@ -13909,19 +14050,20 @@
UserHandle.getUserId(uid), packages[0]);
}
+ @GuardedBy("this")
void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
- uid = uidRec != null ? uidRec.uid : uid;
+ uid = uidRec != null ? uidRec.getUid() : uid;
if (uid < 0) {
throw new IllegalArgumentException("No UidRecord or uid");
}
final int procState = uidRec != null
- ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
+ ? uidRec.getSetProcState() : PROCESS_STATE_NONEXISTENT;
final long procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
- final int capability = uidRec != null ? uidRec.setCapability : 0;
- final boolean ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
+ final int capability = uidRec != null ? uidRec.getSetCapability() : 0;
+ final boolean ephemeral = uidRec != null ? uidRec.isEphemeral() : isEphemeralLocked(uid);
- if (uidRec != null && !uidRec.idle && (change & UidRecord.CHANGE_GONE) != 0) {
+ if (uidRec != null && !uidRec.isIdle() && (change & UidRecord.CHANGE_GONE) != 0) {
// If this uid is going away, and we haven't yet reported it is gone,
// then do so now.
change |= UidRecord.CHANGE_IDLE;
@@ -13930,7 +14072,7 @@
uidRec == null ? null : uidRec.pendingChange,
uid, change, procState, procStateSeq, capability, ephemeral);
if (uidRec != null) {
- uidRec.lastReportedChange = enqueuedChange;
+ uidRec.setLastReportedChange(enqueuedChange);
uidRec.updateLastDispatchedProcStateSeq(enqueuedChange);
}
@@ -13953,21 +14095,21 @@
}
}
- final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
- synchronized (mProcessStats.mLock) {
- if (proc.thread != null) {
- proc.mProfile.setProcessTrackerState(
- proc.getReportedProcState(), memFactor, now);
- }
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ final void setProcessTrackerStateLOSP(ProcessRecord proc, int memFactor, long now) {
+ if (proc.getThread() != null) {
+ proc.mProfile.setProcessTrackerState(
+ proc.mState.getReportedProcState(), memFactor, now);
}
}
@GuardedBy("this")
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
int fgServiceTypes, boolean oomAdj) {
- if (isForeground != proc.hasForegroundServices()
- || proc.getForegroundServiceTypes() != fgServiceTypes) {
- proc.setHasForegroundServices(isForeground, fgServiceTypes);
+ final ProcessServiceRecord psr = proc.mServices;
+ if (isForeground != psr.hasForegroundServices()
+ || psr.getForegroundServiceTypes() != fgServiceTypes) {
+ psr.setHasForegroundServices(isForeground, fgServiceTypes);
ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
proc.info.uid);
if (isForeground) {
@@ -13993,9 +14135,9 @@
}
}
- proc.setReportedForegroundServiceTypes(fgServiceTypes);
+ psr.setReportedForegroundServiceTypes(fgServiceTypes);
ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked(
- proc.pid, proc.info.uid);
+ proc.getPid(), proc.info.uid);
item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
item.foregroundServiceTypes = fgServiceTypes;
}
@@ -14038,7 +14180,6 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
-
}
}
return r;
@@ -14148,17 +14289,20 @@
final int appId = UserHandle.getAppId(pkgUid);
for (int i = mProcessList.mActiveUids.size() - 1; i >= 0; i--) {
final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
- final long bgTime = uidRec.lastBackgroundTime;
- if (bgTime > 0 && !uidRec.idle) {
- if (UserHandle.getAppId(uidRec.uid) == appId) {
+ final long bgTime = uidRec.getLastBackgroundTime();
+ if (bgTime > 0 && !uidRec.isIdle()) {
+ final int uid = uidRec.getUid();
+ if (UserHandle.getAppId(uid) == appId) {
if (userId == UserHandle.USER_ALL
- || userId == UserHandle.getUserId(uidRec.uid)) {
- EventLogTags.writeAmUidIdle(uidRec.uid);
- uidRec.idle = true;
- uidRec.setIdle = true;
- Slog.w(TAG, "Idling uid " + UserHandle.formatUid(uidRec.uid)
+ || userId == UserHandle.getUserId(uid)) {
+ EventLogTags.writeAmUidIdle(uid);
+ synchronized (mProcLock) {
+ uidRec.setIdle(true);
+ uidRec.setSetIdle(true);
+ }
+ Slog.w(TAG, "Idling uid " + UserHandle.formatUid(uid)
+ " from package " + packageName + " user " + userId);
- doStopUidLocked(uidRec.uid, uidRec);
+ doStopUidLocked(uid, uidRec);
}
}
}
@@ -14183,11 +14327,11 @@
final void runInBackgroundDisabled(int uid) {
synchronized (this) {
- UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
+ UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
if (uidRec != null) {
// This uid is actually running... should it be considered background now?
- if (uidRec.idle) {
- doStopUidLocked(uidRec.uid, uidRec);
+ if (uidRec.isIdle()) {
+ doStopUidLocked(uidRec.getUid(), uidRec);
}
} else {
// This uid isn't actually running... still send a report about it being "stopped".
@@ -14241,7 +14385,7 @@
+ callerPid);
return;
}
- if (!pr.mAllowlistManager) {
+ if (!pr.mServices.mAllowlistManager) {
if (checkPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST, callerPid, callerUid)
!= PackageManager.PERMISSION_GRANTED
&& checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callerPid, callerUid)
@@ -14265,13 +14409,15 @@
*/
@GuardedBy("this")
void tempAllowlistUidLocked(int targetUid, long duration, String tag, int type) {
- mPendingTempAllowlist.put(targetUid,
- new PendingTempAllowlist(targetUid, duration, tag, type));
- setUidTempAllowlistStateLocked(targetUid, true);
- mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget();
+ synchronized (mProcLock) {
+ mPendingTempAllowlist.put(targetUid,
+ new PendingTempAllowlist(targetUid, duration, tag, type));
+ setUidTempAllowlistStateLSP(targetUid, true);
+ mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget();
- if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
- mFgsStartTempAllowList.add(targetUid, duration);
+ if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+ mFgsStartTempAllowList.add(targetUid, duration);
+ }
}
}
@@ -14281,7 +14427,7 @@
// First copy out the pending changes... we need to leave them in the map for now,
// in case someone needs to check what is coming up while we don't have the lock held.
- synchronized(this) {
+ synchronized (mProcLock) {
N = mPendingTempAllowlist.size();
list = new PendingTempAllowlist[N];
for (int i = 0; i < N; i++) {
@@ -14301,25 +14447,27 @@
}
// And now we can safely remove them from the map.
- synchronized(this) {
- for (int i = 0; i < N; i++) {
- PendingTempAllowlist ptw = list[i];
- int index = mPendingTempAllowlist.indexOfKey(ptw.targetUid);
- if (index >= 0 && mPendingTempAllowlist.valueAt(index) == ptw) {
- mPendingTempAllowlist.removeAt(index);
+ synchronized (this) {
+ synchronized (mProcLock) {
+ for (int i = 0; i < N; i++) {
+ PendingTempAllowlist ptw = list[i];
+ int index = mPendingTempAllowlist.indexOfKey(ptw.targetUid);
+ if (index >= 0 && mPendingTempAllowlist.valueAt(index) == ptw) {
+ mPendingTempAllowlist.removeAt(index);
+ }
}
}
}
}
- @GuardedBy("this")
- final void setAppIdTempAllowlistStateLocked(int uid, boolean onAllowlist) {
- mOomAdjuster.setAppIdTempAllowlistStateLocked(uid, onAllowlist);
+ @GuardedBy({"this", "mProcLock"})
+ final void setAppIdTempAllowlistStateLSP(int uid, boolean onAllowlist) {
+ mOomAdjuster.setAppIdTempAllowlistStateLSP(uid, onAllowlist);
}
- @GuardedBy("this")
- final void setUidTempAllowlistStateLocked(int uid, boolean onAllowlist) {
- mOomAdjuster.setUidTempAllowlistStateLocked(uid, onAllowlist);
+ @GuardedBy({"this", "mProcLock"})
+ final void setUidTempAllowlistStateLSP(int uid, boolean onAllowlist) {
+ mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
}
private void trimApplications(boolean forceFullOomAdj, String oomAdjReason) {
@@ -14336,26 +14484,28 @@
for (int i = mProcessList.mRemovedProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord app = mProcessList.mRemovedProcesses.get(i);
if (!app.hasActivitiesOrRecentTasks()
- && app.curReceivers.isEmpty() && app.numberOfRunningServices() == 0) {
- Slog.i(
- TAG, "Exiting empty application process "
- + app.toShortString() + " ("
- + (app.thread != null ? app.thread.asBinder() : null)
- + ")\n");
- if (app.pid > 0 && app.pid != MY_PID) {
- app.kill("empty",
+ && app.mReceivers.numberOfCurReceivers() == 0
+ && app.mServices.numberOfRunningServices() == 0) {
+ final IApplicationThread thread = app.getThread();
+ Slog.i(TAG, "Exiting empty application process "
+ + app.toShortString() + " ("
+ + (thread != null ? thread.asBinder() : null)
+ + ")\n");
+ final int pid = app.getPid();
+ if (pid > 0 && pid != MY_PID) {
+ app.killLocked("empty",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
false);
- } else if (app.thread != null) {
+ } else if (thread != null) {
try {
- app.thread.scheduleExit();
+ thread.scheduleExit();
} catch (Exception e) {
// Ignore exceptions.
}
}
didSomething = true;
- cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
+ cleanUpApplicationRecordLocked(app, pid, false, true, -1, false /*replacingPid*/);
mProcessList.mRemovedProcesses.remove(i);
if (app.isPersistent()) {
@@ -14376,7 +14526,7 @@
}
/** This method sends the specified signal to each of the persistent apps */
- public void signalPersistentProcesses(int sig) throws RemoteException {
+ public void signalPersistentProcesses(final int sig) throws RemoteException {
if (sig != SIGNAL_USR1) {
throw new SecurityException("Only SIGNAL_USR1 is allowed");
}
@@ -14387,13 +14537,12 @@
+ android.Manifest.permission.SIGNAL_PERSISTENT_PROCESSES);
}
- synchronized (this) {
- for (int i = mProcessList.mLruProcesses.size() - 1 ; i >= 0 ; i--) {
- ProcessRecord r = mProcessList.mLruProcesses.get(i);
- if (r.thread != null && r.isPersistent()) {
- sendSignal(r.pid, sig);
+ synchronized (mProcLock) {
+ mProcessList.forEachLruProcessesLOSP(false, app -> {
+ if (app.getThread() != null && app.isPersistent()) {
+ sendSignal(app.getPid(), sig);
}
- }
+ });
}
}
@@ -14412,21 +14561,23 @@
}
ProcessRecord proc = null;
- synchronized (this) {
+ synchronized (mProcLock) {
if (process != null) {
- proc = findProcessLocked(process, userId, "profileControl");
+ proc = findProcessLOSP(process, userId, "profileControl");
}
- if (start && (proc == null || proc.thread == null)) {
+ if (start && (proc == null || proc.getThread() == null)) {
throw new IllegalArgumentException("Unknown process: " + process);
}
}
+
synchronized (mAppProfiler.mProfilerLock) {
return mAppProfiler.profileControlLPf(proc, start, profilerInfo, profileType);
}
}
- private ProcessRecord findProcessLocked(String process, int userId, String callName) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ private ProcessRecord findProcessLOSP(String process, int userId, String callName) {
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, callName, null);
ProcessRecord proc = null;
@@ -14439,8 +14590,8 @@
}
if (proc == null) {
- ArrayMap<String, SparseArray<ProcessRecord>> all
- = mProcessList.mProcessNames.getMap();
+ ArrayMap<String, SparseArray<ProcessRecord>> all =
+ mProcessList.getProcessNamesLOSP().getMap();
SparseArray<ProcessRecord> procs = all.get(process);
if (procs != null && procs.size() > 0) {
proc = procs.valueAt(0);
@@ -14462,7 +14613,6 @@
@Override
public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
-
try {
// note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
// its own permission (same as profileControl).
@@ -14476,9 +14626,10 @@
throw new IllegalArgumentException("null fd");
}
- synchronized (this) {
- ProcessRecord proc = findProcessLocked(process, userId, "dumpHeap");
- if (proc == null || proc.thread == null) {
+ synchronized (mProcLock) {
+ ProcessRecord proc = findProcessLOSP(process, userId, "dumpHeap");
+ IApplicationThread thread;
+ if (proc == null || (thread = proc.getThread()) == null) {
throw new IllegalArgumentException("Unknown process: " + process);
}
@@ -14500,7 +14651,7 @@
}
}, null);
- proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
+ thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
fd = null;
return true;
}
@@ -14555,8 +14706,8 @@
}
void onCoreSettingsChange(Bundle settings) {
- synchronized (this) {
- mProcessList.updateCoreSettingsLocked(settings);
+ synchronized (mProcLock) {
+ mProcessList.updateCoreSettingsLOSP(settings);
}
}
@@ -14708,8 +14859,9 @@
return info;
}
- private boolean processSanityChecksLocked(ProcessRecord process) {
- if (process == null || process.thread == null) {
+ @GuardedBy("mProcLock")
+ private boolean processSanityChecksLPr(ProcessRecord process, IApplicationThread thread) {
+ if (process == null || thread == null) {
return false;
}
@@ -14731,34 +14883,36 @@
+ android.Manifest.permission.SET_ACTIVITY_WATCHER);
}
- synchronized (this) {
+ synchronized (mProcLock) {
mBinderTransactionTrackingEnabled = true;
- for (int i = 0; i < mProcessList.mLruProcesses.size(); i++) {
- ProcessRecord process = mProcessList.mLruProcesses.get(i);
- if (!processSanityChecksLocked(process)) {
- continue;
+ mProcessList.forEachLruProcessesLOSP(true, process -> {
+ final IApplicationThread thread = process.getThread();
+ if (!processSanityChecksLPr(process, thread)) {
+ return;
}
try {
- process.thread.startBinderTracking();
+ thread.startBinderTracking();
} catch (RemoteException e) {
Log.v(TAG, "Process disappared");
}
- }
- return true;
+ });
}
+ return true;
}
- public boolean stopBinderTrackingAndDump(ParcelFileDescriptor fd) throws RemoteException {
- try {
- // TODO: hijacking SET_ACTIVITY_WATCHER, but should be changed to its own
- // permission (same as profileControl).
- if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires permission "
- + android.Manifest.permission.SET_ACTIVITY_WATCHER);
- }
+ @Override
+ public boolean stopBinderTrackingAndDump(final ParcelFileDescriptor fd) throws RemoteException {
+ // TODO: hijacking SET_ACTIVITY_WATCHER, but should be changed to its own
+ // permission (same as profileControl).
+ if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+ }
- synchronized (this) {
+ boolean closeFd = true;
+ try {
+ synchronized (mProcLock) {
if (fd == null) {
throw new IllegalArgumentException("null fd");
}
@@ -14766,9 +14920,10 @@
PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd.getFileDescriptor()));
pw.println("Binder transaction traces for all processes.\n");
- for (ProcessRecord process : mProcessList.mLruProcesses) {
- if (!processSanityChecksLocked(process)) {
- continue;
+ mProcessList.forEachLruProcessesLOSP(true, process -> {
+ final IApplicationThread thread = process.getThread();
+ if (!processSanityChecksLPr(process, thread)) {
+ return;
}
pw.println("Traces for process: " + process.processName);
@@ -14776,7 +14931,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- process.thread.stopBinderTrackingAndDump(tp.getWriteFd());
+ thread.stopBinderTrackingAndDump(tp.getWriteFd());
tp.go(fd.getFileDescriptor());
} finally {
tp.kill();
@@ -14790,12 +14945,12 @@
process + ". Exception: " + e);
pw.flush();
}
- }
- fd = null;
+ });
+ closeFd = false;
return true;
}
} finally {
- if (fd != null) {
+ if (fd != null && closeFd) {
try {
fd.close();
} catch (IOException e) {
@@ -14840,12 +14995,12 @@
@Override
public void killForegroundAppsForUser(@UserIdInt int userId) {
- synchronized (ActivityManagerService.this) {
- final ArrayList<ProcessRecord> procs = new ArrayList<>();
- final int NP = mProcessList.mProcessNames.getMap().size();
- for (int ip = 0; ip < NP; ip++) {
+ final ArrayList<ProcessRecord> procs = new ArrayList<>();
+ synchronized (mProcLock) {
+ final int numOfProcs = mProcessList.getProcessNamesLOSP().getMap().size();
+ for (int ip = 0; ip < numOfProcs; ip++) {
final SparseArray<ProcessRecord> apps =
- mProcessList.mProcessNames.getMap().valueAt(ip);
+ mProcessList.getProcessNamesLOSP().getMap().valueAt(ip);
final int NA = apps.size();
for (int ia = 0; ia < NA; ia++) {
final ProcessRecord app = apps.valueAt(ia);
@@ -14853,19 +15008,23 @@
// We don't kill persistent processes.
continue;
}
- if (app.removed
- || (app.userId == userId && app.hasForegroundActivities())) {
+ if (app.isRemoved()
+ || (app.userId == userId && app.mState.hasForegroundActivities())) {
procs.add(app);
}
}
}
+ }
- final int N = procs.size();
- for (int i = 0; i < N; i++) {
- mProcessList.removeProcessLocked(procs.get(i), false, true,
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_KILL_ALL_FG,
- "kill all fg");
+ final int numOfProcs = procs.size();
+ if (numOfProcs > 0) {
+ synchronized (ActivityManagerService.this) {
+ for (int i = 0; i < numOfProcs; i++) {
+ mProcessList.removeProcessLocked(procs.get(i), false, true,
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_KILL_ALL_FG,
+ "kill all fg");
+ }
}
}
}
@@ -14911,22 +15070,26 @@
@Override
public void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids) {
synchronized (ActivityManagerService.this) {
- mDeviceIdleAllowlist = allAppids;
- mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+ synchronized (mProcLock) {
+ mDeviceIdleAllowlist = allAppids;
+ mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+ }
}
}
@Override
public void updateDeviceIdleTempWhitelist(int[] appids, int changingUid, boolean adding,
- long durationMs, @BroadcastOptions.TempAllowListType int type) {
+ long durationMs, @TempAllowListType int type) {
synchronized (ActivityManagerService.this) {
- mDeviceIdleTempAllowlist = appids;
- if (adding) {
- if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
- mFgsStartTempAllowList.add(changingUid, durationMs);
+ synchronized (mProcLock) {
+ mDeviceIdleTempAllowlist = appids;
+ if (adding) {
+ if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+ mFgsStartTempAllowList.add(changingUid, durationMs);
+ }
}
+ setAppIdTempAllowlistStateLSP(changingUid, adding);
}
- setAppIdTempAllowlistStateLocked(changingUid, adding);
}
}
@@ -14964,10 +15127,10 @@
return;
}
}
- if (pr.hasOverlayUi() == hasOverlayUi) {
+ if (pr.mState.hasOverlayUi() == hasOverlayUi) {
return;
}
- pr.setHasOverlayUi(hasOverlayUi);
+ pr.mState.setHasOverlayUi(hasOverlayUi);
//Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
updateOomAdjLocked(pr, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
}
@@ -14985,8 +15148,8 @@
+ uid + " seq: " + procStateSeq);
}
UidRecord record;
- synchronized (ActivityManagerService.this) {
- record = mProcessList.getUidRecordLocked(uid);
+ synchronized (mProcLock) {
+ record = mProcessList.getUidRecordLOSP(uid);
if (record == null) {
if (DEBUG_NETWORK) {
Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
@@ -15049,19 +15212,21 @@
@Override
public boolean isUidActive(int uid) {
- synchronized (ActivityManagerService.this) {
- return isUidActiveLocked(uid);
+ synchronized (mProcLock) {
+ return isUidActiveLOSP(uid);
}
}
@Override
public List<ProcessMemoryState> getMemoryStateForProcesses() {
List<ProcessMemoryState> processMemoryStates = new ArrayList<>();
- synchronized (mPidsSelfLocked) {
- for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
- final ProcessRecord r = mPidsSelfLocked.valueAt(i);
- processMemoryStates.add(
- new ProcessMemoryState(r.uid, r.pid, r.processName, r.curAdj));
+ synchronized (mProcLock) {
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
+ final ProcessRecord r = mPidsSelfLocked.valueAt(i);
+ processMemoryStates.add(new ProcessMemoryState(
+ r.uid, r.getPid(), r.processName, r.mState.getCurAdj()));
+ }
}
}
return processMemoryStates;
@@ -15101,14 +15266,14 @@
final WindowProcessController wpc =
(WindowProcessController) procsToKill.get(i);
final ProcessRecord pr = (ProcessRecord) wpc.mOwner;
- if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
- && pr.curReceivers.isEmpty()) {
- pr.kill("remove task", ApplicationExitInfo.REASON_USER_REQUESTED,
+ if (pr.mState.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND
+ && pr.mReceivers.numberOfCurReceivers() == 0) {
+ pr.killLocked("remove task", ApplicationExitInfo.REASON_USER_REQUESTED,
ApplicationExitInfo.SUBREASON_UNKNOWN, true);
} else {
// We delay killing processes that are not in the background or running a
// receiver.
- pr.waitingToKill = "remove task";
+ pr.setWaitingToKill("remove task");
}
}
}
@@ -15130,18 +15295,15 @@
public boolean hasRunningActivity(int uid, @Nullable String packageName) {
if (packageName == null) return false;
- synchronized (ActivityManagerService.this) {
- for (int i = 0; i < mProcessList.mLruProcesses.size(); i++) {
- final ProcessRecord pr = mProcessList.mLruProcesses.get(i);
- if (pr.uid != uid) {
- continue;
+ synchronized (mProcLock) {
+ return mProcessList.searchEachLruProcessesLOSP(true, app -> {
+ if (app.uid == uid
+ && app.getWindowProcessController().hasRunningActivity(packageName)) {
+ return Boolean.TRUE;
}
- if (pr.getWindowProcessController().hasRunningActivity(packageName)) {
- return true;
- }
- }
+ return null;
+ }) != null;
}
- return false;
}
@Override
@@ -15572,7 +15734,7 @@
}
synchronized (mPidsSelfLocked) {
final ProcessRecord pr = mPidsSelfLocked.get(pid);
- return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.mountMode;
+ return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.getMountMode();
}
}
@@ -15603,19 +15765,17 @@
@Override
public boolean hasRunningForegroundService(int uid, int foregroundServicetype) {
synchronized (ActivityManagerService.this) {
- for (int i = 0; i < mProcessList.mLruProcesses.size(); i++) {
- final ProcessRecord pr = mProcessList.mLruProcesses.get(i);
- if (pr.uid != uid) {
- continue;
+ return mProcessList.searchEachLruProcessesLOSP(true, app -> {
+ if (app.uid != uid) {
+ return null;
}
- if ((pr.getForegroundServiceTypes() & foregroundServicetype) != 0) {
- return true;
+ if ((app.mServices.getForegroundServiceTypes() & foregroundServicetype) != 0) {
+ return Boolean.TRUE;
}
- }
+ return null;
+ }) != null;
}
-
- return false;
}
@Override
@@ -15630,7 +15790,7 @@
@Override
public boolean isUidCurrentlyInstrumented(int uid) {
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
if (!activeInst.mFinished && activeInst.mTargetInfo != null
@@ -15782,8 +15942,8 @@
Slog.d(TAG_NETWORK, "Called from " + callingUid + " to wait for seq: " + procStateSeq);
}
UidRecord record;
- synchronized (this) {
- record = mProcessList.getUidRecordLocked(callingUid);
+ synchronized (mProcLock) {
+ record = mProcessList.getUidRecordLOSP(callingUid);
if (record == null) {
return;
}
@@ -15899,11 +16059,13 @@
}
try {
synchronized(this) {
- mProcessList.killPackageProcessesLocked(packageName, UserHandle.getAppId(pkgUid),
- userId, ProcessList.FOREGROUND_APP_ADJ,
- ApplicationExitInfo.REASON_DEPENDENCY_DIED,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- "dep: " + packageName);
+ synchronized (mProcLock) {
+ mProcessList.killPackageProcessesLSP(packageName, UserHandle.getAppId(pkgUid),
+ userId, ProcessList.FOREGROUND_APP_ADJ,
+ ApplicationExitInfo.REASON_DEPENDENCY_DIED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "dep: " + packageName);
+ }
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -15920,10 +16082,10 @@
enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"scheduleApplicationInfoChanged()");
- synchronized (this) {
+ synchronized (mProcLock) {
final long origId = Binder.clearCallingIdentity();
try {
- updateApplicationInfoLocked(packageNames, userId);
+ updateApplicationInfoLOSP(packageNames, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -15942,12 +16104,13 @@
ActivityThread.currentActivityThread().handleSystemApplicationInfoChanged(ai);
}
- void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ private void updateApplicationInfoLOSP(@NonNull List<String> packagesToUpdate, int userId) {
final boolean updateFrameworkRes = packagesToUpdate.contains("android");
if (updateFrameworkRes) {
PackageParser.readConfigUseRoundIcon(null);
}
- mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes);
+ mProcessList.updateApplicationInfoLOSP(packagesToUpdate, userId, updateFrameworkRes);
if (updateFrameworkRes) {
// Update system server components that need to know about changed overlays. Because the
@@ -15976,7 +16139,7 @@
final int batchSize;
final float threshold;
final BinderCallHeavyHitterListener listener;
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
if (mConstants.BINDER_HEAVY_HITTER_WATCHER_ENABLED) {
// Default watcher takes precedence, ignore the auto sampler.
mHandler.removeMessages(BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG);
@@ -16013,7 +16176,7 @@
final int batchSize;
final float threshold;
final long now;
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
if (!mConstants.BINDER_HEAVY_HITTER_AUTO_SAMPLER_ENABLED) {
// It's configured OFF
return;
@@ -16047,7 +16210,7 @@
* Stop the binder heavy hitter auto sampler after given timeout.
*/
private void handleBinderHeavyHitterAutoSamplerTimeOut() {
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
if (mConstants.BINDER_HEAVY_HITTER_WATCHER_ENABLED) {
// The default watcher is ON, don't bother to stop it.
return;
@@ -16093,10 +16256,11 @@
*/
public void attachAgent(String process, String path) {
try {
- synchronized (this) {
- ProcessRecord proc = findProcessLocked(process, UserHandle.USER_SYSTEM,
+ synchronized (mProcLock) {
+ ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_SYSTEM,
"attachAgent");
- if (proc == null || proc.thread == null) {
+ IApplicationThread thread;
+ if (proc == null || (thread = proc.getThread()) == null) {
throw new IllegalArgumentException("Unknown process: " + process);
}
@@ -16106,7 +16270,7 @@
}
}
- proc.thread.attachAgent(path);
+ thread.attachAgent(path);
}
} catch (RemoteException e) {
throw new IllegalStateException("Process disappeared");
@@ -16175,7 +16339,7 @@
}
// We allow delegation only to one instrumentation started from the shell
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
// If the delegate is already set up for the target UID, nothing to do.
if (mAppOpsService.getAppOpsServiceDelegate() != null) {
if (!(mAppOpsService.getAppOpsServiceDelegate() instanceof ShellDelegate)) {
@@ -16221,7 +16385,7 @@
&& UserHandle.getCallingAppId() != Process.ROOT_UID) {
throw new SecurityException("Only the shell can delegate its permissions");
}
- synchronized (ActivityManagerService.this) {
+ synchronized (mProcLock) {
mAppOpsService.setAppOpsServiceDelegate(null);
getPermissionManagerInternal().stopShellPermissionIdentityDelegation();
}
@@ -16343,7 +16507,7 @@
if (!isCallerShell()) {
throw new SecurityException("Only shell can call it");
}
- synchronized (this) {
+ synchronized (mProcLock) {
try {
if (mLifeMonitorFds == null) {
mLifeMonitorFds = ParcelFileDescriptor.createPipe();
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index a7119d1..34d9a60 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -160,7 +160,7 @@
}
void appNotResponding(boolean onlyDumpSelf) {
- mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
+ mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
onlyDumpSelf);
}
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index 779d378..48222cb 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -38,6 +38,7 @@
final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListener {
private final ActivityManagerService mService;
+ private final ActivityManagerGlobalLock mProcLock;
private final AppErrorResult mResult;
private final ProcessRecord mProc;
private final boolean mIsRestartable;
@@ -63,6 +64,7 @@
Resources res = context.getResources();
mService = service;
+ mProcLock = service.mProcLock;
mProc = data.proc;
mResult = data.result;
mIsRestartable = (data.taskId != INVALID_TASK_ID || data.isRestartableForService)
@@ -71,7 +73,7 @@
BidiFormatter bidi = BidiFormatter.getInstance();
CharSequence name;
- if ((mProc.getPkgList().size() == 1)
+ if (mProc.getPkgList().size() == 1
&& (name = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
setTitle(res.getString(
data.repeating ? com.android.internal.R.string.aerr_application_repeated
@@ -112,7 +114,7 @@
LayoutInflater.from(context).inflate(
com.android.internal.R.layout.app_error_dialog, frame, true);
- final boolean hasReceiver = mProc.errorReportReceiver != null;
+ final boolean hasReceiver = mProc.mErrorState.getErrorReportReceiver() != null;
final TextView restart = findViewById(com.android.internal.R.id.aerr_restart);
restart.setOnClickListener(this);
@@ -166,11 +168,11 @@
}
private void setResult(int result) {
- synchronized (mService) {
+ synchronized (mProcLock) {
if (mProc != null) {
// Don't dismiss again since it leads to recursive call between dismiss and this
// method.
- mProc.getDialogController().clearCrashDialogs(false /* needDismiss */);
+ mProc.mErrorState.getDialogController().clearCrashDialogs(false /* needDismiss */);
}
}
mResult.set(result);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 7be54c2..e5a5cff 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -72,6 +72,7 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "AppErrors" : TAG_AM;
private final ActivityManagerService mService;
+ private final ActivityManagerGlobalLock mProcLock;
private final Context mContext;
private final PackageWatchdog mPackageWatchdog;
@@ -137,6 +138,7 @@
AppErrors(Context context, ActivityManagerService service, PackageWatchdog watchdog) {
context.assertRuntimeOverlayThemable();
mService = service;
+ mProcLock = service.mProcLock;
mContext = context;
mPackageWatchdog = watchdog;
}
@@ -154,17 +156,48 @@
}
}
- void dumpDebug(ProtoOutputStream proto, long fieldId, String dumpPackage) {
- synchronized (mBadProcessLock) {
- final ProcessMap<BadProcessInfo> badProcesses = mBadProcesses;
- if (mProcessCrashTimes.getMap().isEmpty() && badProcesses.getMap().isEmpty()) {
- return;
+ @GuardedBy("mProcLock")
+ void dumpDebugLPr(ProtoOutputStream proto, long fieldId, String dumpPackage) {
+ final ProcessMap<BadProcessInfo> badProcesses = mBadProcesses;
+ if (mProcessCrashTimes.getMap().isEmpty() && badProcesses.getMap().isEmpty()) {
+ return;
+ }
+
+ final long token = proto.start(fieldId);
+ final long now = SystemClock.uptimeMillis();
+ proto.write(AppErrorsProto.NOW_UPTIME_MS, now);
+
+ if (!badProcesses.getMap().isEmpty()) {
+ final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = badProcesses.getMap();
+ final int processCount = pmap.size();
+ for (int ip = 0; ip < processCount; ip++) {
+ final long btoken = proto.start(AppErrorsProto.BAD_PROCESSES);
+ final String pname = pmap.keyAt(ip);
+ final SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
+ final int uidCount = uids.size();
+
+ proto.write(AppErrorsProto.BadProcess.PROCESS_NAME, pname);
+ for (int i = 0; i < uidCount; i++) {
+ final int puid = uids.keyAt(i);
+ final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
+ if (dumpPackage != null && (r == null
+ || !r.getPkgList().containsKey(dumpPackage))) {
+ continue;
+ }
+ final BadProcessInfo info = uids.valueAt(i);
+ final long etoken = proto.start(AppErrorsProto.BadProcess.ENTRIES);
+ proto.write(AppErrorsProto.BadProcess.Entry.UID, puid);
+ proto.write(AppErrorsProto.BadProcess.Entry.CRASHED_AT_MS, info.time);
+ proto.write(AppErrorsProto.BadProcess.Entry.SHORT_MSG, info.shortMsg);
+ proto.write(AppErrorsProto.BadProcess.Entry.LONG_MSG, info.longMsg);
+ proto.write(AppErrorsProto.BadProcess.Entry.STACK, info.stack);
+ proto.end(etoken);
+ }
+ proto.end(btoken);
}
+ }
- final long token = proto.start(fieldId);
- final long now = SystemClock.uptimeMillis();
- proto.write(AppErrorsProto.NOW_UPTIME_MS, now);
-
+ synchronized (mBadProcessLock) {
if (!mProcessCrashTimes.getMap().isEmpty()) {
final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
final int procCount = pmap.size();
@@ -177,7 +210,7 @@
proto.write(AppErrorsProto.ProcessCrashTime.PROCESS_NAME, pname);
for (int i = 0; i < uidCount; i++) {
final int puid = uids.keyAt(i);
- final ProcessRecord r = mService.getProcessNames().get(pname, puid);
+ final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
if (dumpPackage != null
&& (r == null || !r.getPkgList().containsKey(dumpPackage))) {
continue;
@@ -190,44 +223,14 @@
}
proto.end(ctoken);
}
-
}
-
- if (!badProcesses.getMap().isEmpty()) {
- final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = badProcesses.getMap();
- final int processCount = pmap.size();
- for (int ip = 0; ip < processCount; ip++) {
- final long btoken = proto.start(AppErrorsProto.BAD_PROCESSES);
- final String pname = pmap.keyAt(ip);
- final SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
- final int uidCount = uids.size();
-
- proto.write(AppErrorsProto.BadProcess.PROCESS_NAME, pname);
- for (int i = 0; i < uidCount; i++) {
- final int puid = uids.keyAt(i);
- final ProcessRecord r = mService.getProcessNames().get(pname, puid);
- if (dumpPackage != null && (r == null
- || !r.getPkgList().containsKey(dumpPackage))) {
- continue;
- }
- final BadProcessInfo info = uids.valueAt(i);
- final long etoken = proto.start(AppErrorsProto.BadProcess.ENTRIES);
- proto.write(AppErrorsProto.BadProcess.Entry.UID, puid);
- proto.write(AppErrorsProto.BadProcess.Entry.CRASHED_AT_MS, info.time);
- proto.write(AppErrorsProto.BadProcess.Entry.SHORT_MSG, info.shortMsg);
- proto.write(AppErrorsProto.BadProcess.Entry.LONG_MSG, info.longMsg);
- proto.write(AppErrorsProto.BadProcess.Entry.STACK, info.stack);
- proto.end(etoken);
- }
- proto.end(btoken);
- }
- }
-
- proto.end(token);
}
+
+ proto.end(token);
}
- boolean dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage) {
+ @GuardedBy("mProcLock")
+ boolean dumpLPr(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage) {
final long now = SystemClock.uptimeMillis();
synchronized (mBadProcessLock) {
if (!mProcessCrashTimes.getMap().isEmpty()) {
@@ -240,9 +243,9 @@
final int uidCount = uids.size();
for (int i = 0; i < uidCount; i++) {
final int puid = uids.keyAt(i);
- final ProcessRecord r = mService.getProcessNames().get(pname, puid);
- if (dumpPackage != null && (r == null
- || !r.getPkgList().containsKey(dumpPackage))) {
+ final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
+ if (dumpPackage != null
+ && (r == null || !r.getPkgList().containsKey(dumpPackage))) {
continue;
}
if (!printed) {
@@ -271,7 +274,7 @@
final int uidCount = uids.size();
for (int i = 0; i < uidCount; i++) {
final int puid = uids.keyAt(i);
- final ProcessRecord r = mService.getProcessNames().get(pname, puid);
+ final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
if (dumpPackage != null
&& (r == null || !r.getPkgList().containsKey(dumpPackage))) {
continue;
@@ -303,7 +306,7 @@
final int uidCount = uids.size();
for (int i = 0; i < uidCount; i++) {
final int puid = uids.keyAt(i);
- final ProcessRecord r = mService.getProcessNames().get(pname, puid);
+ final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
if (dumpPackage != null && (r == null
|| !r.getPkgList().containsKey(dumpPackage))) {
continue;
@@ -330,14 +333,14 @@
for (int pos = 0; pos < info.stack.length(); pos++) {
if (info.stack.charAt(pos) == '\n') {
pw.print(" ");
- pw.write(info.stack, lastPos, pos-lastPos);
+ pw.write(info.stack, lastPos, pos - lastPos);
pw.println();
- lastPos = pos+1;
+ lastPos = pos + 1;
}
}
if (lastPos < info.stack.length()) {
pw.print(" ");
- pw.write(info.stack, lastPos, info.stack.length()-lastPos);
+ pw.write(info.stack, lastPos, info.stack.length() - lastPos);
pw.println();
}
}
@@ -437,32 +440,38 @@
}
}
+ @GuardedBy("mService")
void killAppAtUserRequestLocked(ProcessRecord app) {
- ProcessRecord.ErrorDialogController controller =
- app.getDialogController();
+ ErrorDialogController controller = app.mErrorState.getDialogController();
int reasonCode = ApplicationExitInfo.REASON_ANR;
int subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
- if (controller.hasDebugWaitingDialog()) {
- reasonCode = ApplicationExitInfo.REASON_OTHER;
- subReason = ApplicationExitInfo.SUBREASON_WAIT_FOR_DEBUGGER;
+ synchronized (mProcLock) {
+ if (controller.hasDebugWaitingDialog()) {
+ reasonCode = ApplicationExitInfo.REASON_OTHER;
+ subReason = ApplicationExitInfo.SUBREASON_WAIT_FOR_DEBUGGER;
+ }
+ controller.clearAllErrorDialogs();
+ killAppImmediateLSP(app, reasonCode, subReason,
+ "user-terminated", "user request after error");
}
-
- controller.clearAllErrorDialogs();
- killAppImmediateLocked(app, reasonCode, subReason,
- "user-terminated", "user request after error");
}
- private void killAppImmediateLocked(ProcessRecord app, int reasonCode, int subReason,
+ @GuardedBy({"mService", "mProcLock"})
+ private void killAppImmediateLSP(ProcessRecord app, int reasonCode, int subReason,
String reason, String killReason) {
- app.setCrashing(false);
- app.crashingReport = null;
- app.setNotResponding(false);
- app.notRespondingReport = null;
- if (app.pid > 0 && app.pid != MY_PID) {
- handleAppCrashLocked(app, reason,
- null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
- app.kill(killReason, reasonCode, subReason, true);
+ final ProcessErrorStateRecord errState = app.mErrorState;
+ errState.setCrashing(false);
+ errState.setCrashingReport(null);
+ errState.setNotResponding(false);
+ errState.setNotRespondingReport(null);
+ final int pid = errState.mApp.getPid();
+ if (pid > 0 && pid != MY_PID) {
+ synchronized (mBadProcessLock) {
+ handleAppCrashLSPB(app, reason,
+ null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
+ }
+ app.killLocked(killReason, reasonCode, subReason, true);
}
}
@@ -489,7 +498,7 @@
if (uid >= 0 && p.uid != uid) {
continue;
}
- if (p.pid == initialPid) {
+ if (p.getPid() == initialPid) {
proc = p;
break;
}
@@ -508,7 +517,7 @@
return;
}
- proc.scheduleCrash(message);
+ proc.scheduleCrashLocked(message);
if (force) {
// If the app is responsive, the scheduled crash will happen as expected
// and then the delayed summary kill will be a no-op.
@@ -516,9 +525,11 @@
mService.mHandler.postDelayed(
() -> {
synchronized (mService) {
- killAppImmediateLocked(p, ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_INVALID_STATE,
- "forced", "killed for invalid state");
+ synchronized (mProcLock) {
+ killAppImmediateLSP(p, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_INVALID_STATE,
+ "forced", "killed for invalid state");
+ }
}
},
5000L);
@@ -627,52 +638,54 @@
if (res == AppErrorDialog.TIMEOUT || res == AppErrorDialog.CANCEL) {
res = AppErrorDialog.FORCE_QUIT;
}
- if (res == AppErrorDialog.MUTE) {
- synchronized (mBadProcessLock) {
- stopReportingCrashesLBp(r);
- }
- }
- if (res == AppErrorDialog.RESTART) {
- synchronized (mService) {
- mService.mProcessList.removeProcessLocked(r, false, true,
- ApplicationExitInfo.REASON_CRASH, "crash");
- }
- if (taskId != INVALID_TASK_ID) {
- try {
- mService.startActivityFromRecents(taskId,
- ActivityOptions.makeBasic().toBundle());
- } catch (IllegalArgumentException e) {
- // Hmm...that didn't work. Task should either be in recents or associated
- // with a stack.
- Slog.e(TAG, "Could not restart taskId=" + taskId, e);
+ switch (res) {
+ case AppErrorDialog.MUTE:
+ synchronized (mBadProcessLock) {
+ stopReportingCrashesLBp(r);
}
- }
- }
- if (res == AppErrorDialog.FORCE_QUIT) {
- final long orig = Binder.clearCallingIdentity();
- try {
- // Kill it with fire!
- mService.mAtmInternal.onHandleAppCrash(r.getWindowProcessController());
- if (!r.isPersistent()) {
- synchronized (mService) {
- mService.mProcessList.removeProcessLocked(r, false, false,
- ApplicationExitInfo.REASON_CRASH, "crash");
+ break;
+ case AppErrorDialog.RESTART:
+ synchronized (mService) {
+ mService.mProcessList.removeProcessLocked(r, false, true,
+ ApplicationExitInfo.REASON_CRASH, "crash");
+ }
+ if (taskId != INVALID_TASK_ID) {
+ try {
+ mService.startActivityFromRecents(taskId,
+ ActivityOptions.makeBasic().toBundle());
+ } catch (IllegalArgumentException e) {
+ // Hmm...that didn't work. Task should either be in recents or associated
+ // with a stack.
+ Slog.e(TAG, "Could not restart taskId=" + taskId, e);
}
- mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
}
- } finally {
- Binder.restoreCallingIdentity(orig);
- }
- }
- if (res == AppErrorDialog.APP_INFO) {
- appErrorIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- appErrorIntent.setData(Uri.parse("package:" + r.info.packageName));
- appErrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
- synchronized (mService) {
- appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
- }
+ break;
+ case AppErrorDialog.FORCE_QUIT:
+ final long orig = Binder.clearCallingIdentity();
+ try {
+ // Kill it with fire!
+ mService.mAtmInternal.onHandleAppCrash(r.getWindowProcessController());
+ if (!r.isPersistent()) {
+ synchronized (mService) {
+ mService.mProcessList.removeProcessLocked(r, false, false,
+ ApplicationExitInfo.REASON_CRASH, "crash");
+ }
+ mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(orig);
+ }
+ break;
+ case AppErrorDialog.APP_INFO:
+ appErrorIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ appErrorIntent.setData(Uri.parse("package:" + r.info.packageName));
+ appErrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ break;
+ case AppErrorDialog.FORCE_QUIT_AND_REPORT:
+ synchronized (mProcLock) {
+ appErrorIntent = createAppErrorIntentLOSP(r, timeMillis, crashInfo);
+ }
+ break;
}
if (appErrorIntent != null) {
@@ -684,13 +697,14 @@
}
}
+ @GuardedBy("mService")
private boolean handleAppCrashInActivityController(ProcessRecord r,
ApplicationErrorReport.CrashInfo crashInfo,
String shortMsg, String longMsg,
String stackTrace, long timeMillis,
int callingPid, int callingUid) {
String name = r != null ? r.processName : null;
- int pid = r != null ? r.pid : callingPid;
+ int pid = r != null ? r.getPid() : callingPid;
int uid = r != null ? r.info.uid : callingUid;
return mService.mAtmInternal.handleAppCrashInActivityController(
@@ -703,7 +717,7 @@
Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request");
if (r != null) {
if (!makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, null)) {
- r.kill("crash", ApplicationExitInfo.REASON_CRASH, true);
+ r.killLocked("crash", ApplicationExitInfo.REASON_CRASH, true);
}
} else {
// Huh.
@@ -718,15 +732,22 @@
});
}
+ @GuardedBy("mService")
private boolean makeAppCrashingLocked(ProcessRecord app,
String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
- app.setCrashing(true);
- app.crashingReport = generateProcessError(app,
- ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
- app.startAppProblemLocked();
- app.getWindowProcessController().stopFreezingActivities();
- return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace,
- data);
+ synchronized (mProcLock) {
+ final ProcessErrorStateRecord errState = app.mErrorState;
+ errState.setCrashing(true);
+ errState.setCrashingReport(generateProcessError(app,
+ ActivityManager.ProcessErrorStateInfo.CRASHED,
+ null, shortMsg, longMsg, stackTrace));
+ errState.startAppProblemLSP();
+ app.getWindowProcessController().stopFreezingActivities();
+ synchronized (mBadProcessLock) {
+ return handleAppCrashLSPB(app, "force-crash" /*reason*/, shortMsg, longMsg,
+ stackTrace, data);
+ }
+ }
}
/**
@@ -748,7 +769,7 @@
report.condition = condition;
report.processName = app.processName;
- report.pid = app.pid;
+ report.pid = app.getPid();
report.uid = app.info.uid;
report.tag = activity;
report.shortMsg = shortMsg;
@@ -758,170 +779,157 @@
return report;
}
- Intent createAppErrorIntentLocked(ProcessRecord r,
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ Intent createAppErrorIntentLOSP(ProcessRecord r,
long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
- ApplicationErrorReport report = createAppErrorReportLocked(r, timeMillis, crashInfo);
+ ApplicationErrorReport report = createAppErrorReportLOSP(r, timeMillis, crashInfo);
if (report == null) {
return null;
}
Intent result = new Intent(Intent.ACTION_APP_ERROR);
- result.setComponent(r.errorReportReceiver);
+ result.setComponent(r.mErrorState.getErrorReportReceiver());
result.putExtra(Intent.EXTRA_BUG_REPORT, report);
result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return result;
}
- private ApplicationErrorReport createAppErrorReportLocked(ProcessRecord r,
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ private ApplicationErrorReport createAppErrorReportLOSP(ProcessRecord r,
long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
- if (r.errorReportReceiver == null) {
+ final ProcessErrorStateRecord errState = r.mErrorState;
+ if (errState.getErrorReportReceiver() == null) {
return null;
}
- if (!r.isCrashing() && !r.isNotResponding() && !r.forceCrashReport) {
+ if (!errState.isCrashing() && !errState.isNotResponding()
+ && !errState.isForceCrashReport()) {
return null;
}
ApplicationErrorReport report = new ApplicationErrorReport();
report.packageName = r.info.packageName;
- report.installerPackageName = r.errorReportReceiver.getPackageName();
+ report.installerPackageName = errState.getErrorReportReceiver().getPackageName();
report.processName = r.processName;
report.time = timeMillis;
report.systemApp = (r.info.flags & FLAG_SYSTEM) != 0;
- if (r.isCrashing() || r.forceCrashReport) {
+ if (errState.isCrashing() || errState.isForceCrashReport()) {
report.type = ApplicationErrorReport.TYPE_CRASH;
report.crashInfo = crashInfo;
- } else if (r.isNotResponding()) {
+ } else if (errState.isNotResponding()) {
report.type = ApplicationErrorReport.TYPE_ANR;
report.anrInfo = new ApplicationErrorReport.AnrInfo();
- report.anrInfo.activity = r.notRespondingReport.tag;
- report.anrInfo.cause = r.notRespondingReport.shortMsg;
- report.anrInfo.info = r.notRespondingReport.longMsg;
+ report.anrInfo.activity = errState.getNotRespondingReport().tag;
+ report.anrInfo.cause = errState.getNotRespondingReport().shortMsg;
+ report.anrInfo.info = errState.getNotRespondingReport().longMsg;
}
return report;
}
- private boolean handleAppCrashLocked(ProcessRecord app, String reason,
+ @GuardedBy({"mService", "mProcLock", "mBadProcessLock"})
+ private boolean handleAppCrashLSPB(ProcessRecord app, String reason,
String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
final long now = SystemClock.uptimeMillis();
final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0,
mService.mUserController.getCurrentUserId()) != 0;
- final boolean procIsBoundForeground =
- (app.getCurProcState() == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-
Long crashTime;
Long crashTimePersistent;
- boolean tryAgain = false;
+ final String processName = app.processName;
+ final int uid = app.uid;
+ final int userId = app.userId;
+ final boolean isolated = app.isolated;
+ final boolean persistent = app.isPersistent();
+ final WindowProcessController proc = app.getWindowProcessController();
+ final ProcessErrorStateRecord errState = app.mErrorState;
- synchronized (mBadProcessLock) {
- if (!app.isolated) {
- crashTime = mProcessCrashTimes.get(app.processName, app.uid);
- crashTimePersistent = mProcessCrashTimesPersistent.get(app.processName, app.uid);
- } else {
- crashTime = crashTimePersistent = null;
- }
+ if (!app.isolated) {
+ crashTime = mProcessCrashTimes.get(processName, uid);
+ crashTimePersistent = mProcessCrashTimesPersistent.get(processName, uid);
+ } else {
+ crashTime = crashTimePersistent = null;
+ }
- // Bump up the crash count of any services currently running in the proc.
- for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
- // Any services running in the application need to be placed
- // back in the pending list.
- ServiceRecord sr = app.getRunningServiceAt(i);
- // If the service was restarted a while ago, then reset crash count,
- // else increment it.
- if (now > sr.restartTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
- sr.crashCount = 1;
- } else {
- sr.crashCount++;
- }
- // Allow restarting for started or bound foreground services that are crashing.
- // This includes wallpapers.
- if (sr.crashCount < mService.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
- && (sr.isForeground || procIsBoundForeground)) {
- tryAgain = true;
- }
- }
+ // Bump up the crash count of any services currently running in the proc.
+ boolean tryAgain = app.mServices.incServiceCrashCountLocked(now);
- final boolean quickCrash = crashTime != null
- && now < crashTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
- if (quickCrash || isProcOverCrashLimitLBp(app, now)) {
- // The process either crashed again very quickly or has been crashing periodically
- // in the last few hours. If it was a bound foreground service, let's try to
- // restart again in a while, otherwise the process loses!
- Slog.w(TAG, "Process " + app.processName + " has crashed too many times, killing!"
- + " Reason: "
- + (quickCrash ? "crashed quickly" : "over process crash limit"));
- EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
- app.userId, app.processName, app.uid);
- mService.mAtmInternal.onHandleAppCrash(app.getWindowProcessController());
- if (!app.isPersistent()) {
- // We don't want to start this process again until the user
- // explicitly does so... but for persistent process, we really
- // need to keep it running. If a persistent process is actually
- // repeatedly crashing, then badness for everyone.
- EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
- app.processName);
- if (!app.isolated) {
- // XXX We don't have a way to mark isolated processes
- // as bad, since they don't have a persistent identity.
- markBadProcess(app.processName, app.uid,
- new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
- mProcessCrashTimes.remove(app.processName, app.uid);
- mProcessCrashCounts.remove(app.processName, app.uid);
- }
- app.bad = true;
- app.removed = true;
- // Don't let services in this process be restarted and potentially
- // annoy the user repeatedly. Unless it is persistent, since those
- // processes run critical code.
- mService.mProcessList.removeProcessLocked(app, false, tryAgain,
- ApplicationExitInfo.REASON_CRASH, "crash");
- mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
- if (!showBackground) {
- return false;
- }
+ final boolean quickCrash = crashTime != null
+ && now < crashTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
+ if (quickCrash || isProcOverCrashLimitLBp(app, now)) {
+ // The process either crashed again very quickly or has been crashing periodically in
+ // the last few hours. If it was a bound foreground service, let's try to restart again
+ // in a while, otherwise the process loses!
+ Slog.w(TAG, "Process " + processName + " has crashed too many times, killing!"
+ + " Reason: " + (quickCrash ? "crashed quickly" : "over process crash limit"));
+ EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
+ userId, processName, uid);
+ mService.mAtmInternal.onHandleAppCrash(proc);
+ if (!persistent) {
+ // We don't want to start this process again until the user
+ // explicitly does so... but for persistent process, we really
+ // need to keep it running. If a persistent process is actually
+ // repeatedly crashing, then badness for everyone.
+ EventLog.writeEvent(EventLogTags.AM_PROC_BAD, userId, uid,
+ processName);
+ if (!isolated) {
+ // XXX We don't have a way to mark isolated processes
+ // as bad, since they don't have a persistent identity.
+ markBadProcess(processName, app.uid,
+ new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
+ mProcessCrashTimes.remove(processName, app.uid);
+ mProcessCrashCounts.remove(processName, app.uid);
}
+ errState.setBad(true);
+ app.setRemoved(true);
+ // Don't let services in this process be restarted and potentially
+ // annoy the user repeatedly. Unless it is persistent, since those
+ // processes run critical code.
+ mService.mProcessList.removeProcessLocked(app, false, tryAgain,
+ ApplicationExitInfo.REASON_CRASH, "crash");
mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
- } else {
- final int affectedTaskId = mService.mAtmInternal.finishTopCrashedActivities(
- app.getWindowProcessController(), reason);
- if (data != null) {
- data.taskId = affectedTaskId;
- }
- if (data != null && crashTimePersistent != null
- && now < crashTimePersistent
- + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
- data.repeating = true;
+ if (!showBackground) {
+ return false;
}
}
-
- if (data != null && tryAgain) {
- data.isRestartableForService = true;
+ mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
+ } else {
+ final int affectedTaskId = mService.mAtmInternal.finishTopCrashedActivities(
+ proc, reason);
+ if (data != null) {
+ data.taskId = affectedTaskId;
}
-
- // If the crashing process is what we consider to be the "home process" and it has been
- // replaced by a third-party app, clear the package preferred activities from packages
- // with a home activity running in the process to prevent a repeatedly crashing app
- // from blocking the user to manually clear the list.
- final WindowProcessController proc = app.getWindowProcessController();
- if (proc.isHomeProcess() && proc.hasActivities()
- && (app.info.flags & FLAG_SYSTEM) == 0) {
- proc.clearPackagePreferredForHomeActivities();
- }
-
- if (!app.isolated) {
- // XXX Can't keep track of crash times for isolated processes,
- // because they don't have a persistent identity.
- mProcessCrashTimes.put(app.processName, app.uid, now);
- mProcessCrashTimesPersistent.put(app.processName, app.uid, now);
- updateProcessCrashCountLBp(app.processName, app.uid, now);
+ if (data != null && crashTimePersistent != null
+ && now < crashTimePersistent + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
+ data.repeating = true;
}
}
- if (app.crashHandler != null) mService.mHandler.post(app.crashHandler);
+ if (data != null && tryAgain) {
+ data.isRestartableForService = true;
+ }
+
+ // If the crashing process is what we consider to be the "home process" and it has been
+ // replaced by a third-party app, clear the package preferred activities from packages
+ // with a home activity running in the process to prevent a repeatedly crashing app
+ // from blocking the user to manually clear the list.
+ if (proc.isHomeProcess() && proc.hasActivities() && (app.info.flags & FLAG_SYSTEM) == 0) {
+ proc.clearPackagePreferredForHomeActivities();
+ }
+
+ if (!isolated) {
+ // XXX Can't keep track of crash times for isolated processes,
+ // because they don't have a persistent identity.
+ mProcessCrashTimes.put(processName, uid, now);
+ mProcessCrashTimesPersistent.put(processName, uid, now);
+ updateProcessCrashCountLBp(processName, uid, now);
+ }
+
+ if (errState.getCrashHandler() != null) {
+ mService.mHandler.post(errState.getCrashHandler());
+ }
return true;
}
@@ -951,15 +959,16 @@
mService.mUserController.getCurrentUserId()) != 0;
final int userId;
- synchronized (mService) {
+ synchronized (mProcLock) {
final ProcessRecord proc = data.proc;
final AppErrorResult res = data.result;
if (proc == null) {
Slog.e(TAG, "handleShowAppErrorUi: proc is null");
return;
}
+ final ProcessErrorStateRecord errState = proc.mErrorState;
userId = proc.userId;
- if (proc.getDialogController().hasCrashDialogs()) {
+ if (errState.getDialogController().hasCrashDialogs()) {
Slog.e(TAG, "App already has crash dialog: " + proc);
if (res != null) {
res.set(AppErrorDialog.ALREADY_SHOWING);
@@ -968,7 +977,7 @@
}
boolean isBackground = (UserHandle.getAppId(proc.uid)
>= Process.FIRST_APPLICATION_UID
- && proc.pid != MY_PID);
+ && proc.getPid() != MY_PID);
for (int profileId : mService.mUserController.getCurrentProfileIds()) {
isBackground &= (userId != profileId);
}
@@ -1001,7 +1010,7 @@
if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
&& !crashSilenced && !shouldThottle
&& (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
- proc.getDialogController().showCrashDialogs(data);
+ errState.getDialogController().showCrashDialogs(data);
if (!proc.isolated) {
mProcessCrashShowDialogTimes.put(proc.processName, proc.uid, now);
}
@@ -1026,17 +1035,19 @@
void handleShowAnrUi(Message msg) {
List<VersionedPackage> packageList = null;
- synchronized (mService) {
- AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
- final ProcessRecord proc = data.proc;
- if (proc == null) {
- Slog.e(TAG, "handleShowAnrUi: proc is null");
- return;
- }
+ boolean doKill = false;
+ AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
+ final ProcessRecord proc = data.proc;
+ if (proc == null) {
+ Slog.e(TAG, "handleShowAnrUi: proc is null");
+ return;
+ }
+ synchronized (mProcLock) {
+ final ProcessErrorStateRecord errState = proc.mErrorState;
if (!proc.isPersistent()) {
packageList = proc.getPackageListWithVersionCode();
}
- if (proc.getDialogController().hasAnrDialogs()) {
+ if (errState.getDialogController().hasAnrDialogs()) {
Slog.e(TAG, "App already has anr dialog: " + proc);
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.ALREADY_SHOWING);
@@ -1047,14 +1058,17 @@
Settings.Secure.ANR_SHOW_BACKGROUND, 0,
mService.mUserController.getCurrentUserId()) != 0;
if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
- proc.getDialogController().showAnrDialogs(data);
+ errState.getDialogController().showAnrDialogs(data);
} else {
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.CANT_SHOW);
// Just kill the app if there is no dialog to be shown.
- mService.killAppAtUsersRequest(proc);
+ doKill = true;
}
}
+ if (doKill) {
+ mService.killAppAtUsersRequest(proc);
+ }
// Notify PackageWatchdog without the lock held
if (packageList != null) {
mPackageWatchdog.onPackageFailure(packageList,
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 48aa8be..17be210 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -920,20 +920,20 @@
info = new ApplicationExitInfo();
}
- synchronized (mService) {
- final int definingUid = app.hostingRecord != null
- ? app.hostingRecord.getDefiningUid() : 0;
- info.setPid(app.pid);
+ synchronized (mService.mProcLock) {
+ final int definingUid = app.getHostingRecord() != null
+ ? app.getHostingRecord().getDefiningUid() : 0;
+ info.setPid(app.getPid());
info.setRealUid(app.uid);
info.setPackageUid(app.info.uid);
info.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
info.setProcessName(app.processName);
- info.setConnectionGroup(app.connectionGroup);
+ info.setConnectionGroup(app.mServices.getConnectionGroup());
info.setPackageName(app.info.packageName);
info.setPackageList(app.getPackageList());
info.setReason(ApplicationExitInfo.REASON_UNKNOWN);
info.setStatus(0);
- info.setImportance(procStateToImportance(app.setProcState));
+ info.setImportance(procStateToImportance(app.mState.getSetProcState()));
info.setPss(app.mProfile.getLastPss());
info.setRss(app.mProfile.getLastRss());
info.setTimestamp(System.currentTimeMillis());
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index 96e6f6e..77d2898 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -63,7 +63,7 @@
? data.aInfo.loadLabel(context.getPackageManager())
: null;
CharSequence name2 = null;
- if ((mProc.getPkgList().size() == 1)
+ if (mProc.getPkgList().size() == 1
&& (name2 = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
if (name1 != null) {
resid = com.android.internal.R.string.anr_activity_application;
@@ -108,7 +108,7 @@
final TextView report = findViewById(com.android.internal.R.id.aerr_report);
report.setOnClickListener(this);
- final boolean hasReceiver = mProc.errorReportReceiver != null;
+ final boolean hasReceiver = mProc.mErrorState.getErrorReportReceiver() != null;
report.setVisibility(hasReceiver ? View.VISIBLE : View.GONE);
final TextView close = findViewById(com.android.internal.R.id.aerr_close);
close.setOnClickListener(this);
@@ -152,15 +152,18 @@
// Continue waiting for the application.
synchronized (mService) {
ProcessRecord app = mProc;
+ final ProcessErrorStateRecord errState = app.mErrorState;
if (msg.what == WAIT_AND_REPORT) {
- appErrorIntent = mService.mAppErrors.createAppErrorIntentLocked(app,
+ appErrorIntent = mService.mAppErrors.createAppErrorIntentLOSP(app,
System.currentTimeMillis(), null);
}
- app.setNotResponding(false);
- app.notRespondingReport = null;
- app.getDialogController().clearAnrDialogs();
+ synchronized (mService.mProcLock) {
+ errState.setNotResponding(false);
+ errState.setNotRespondingReport(null);
+ errState.getDialogController().clearAnrDialogs();
+ }
mService.mServices.scheduleServiceTimeoutLocked(app);
}
break;
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3df058c..8f11a5a 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -207,7 +207,6 @@
*/
private volatile boolean mTestPssMode = false;
- @GuardedBy("mService")
private final LowMemDetector mLowMemDetector;
/**
@@ -239,13 +238,13 @@
/**
* Total time spent with RAM that has been added in the past since the last idle time.
*/
- @GuardedBy("mService")
+ @GuardedBy("mProcLock")
private long mLowRamTimeSinceLastIdle = 0;
/**
* If RAM is currently low, when that horrible situation started.
*/
- @GuardedBy("mService")
+ @GuardedBy("mProcLock")
private long mLowRamStartTime = 0;
/**
@@ -331,6 +330,8 @@
*/
final Object mProfilerLock = new Object();
+ final ActivityManagerGlobalLock mProcLock;
+
/**
* Observe DeviceConfig changes to the PSS calculation interval
*/
@@ -852,8 +853,8 @@
/**
* Schedule PSS collection of all processes.
*/
- @GuardedBy("mService")
- void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) {
+ @GuardedBy("mProcLock")
+ void requestPssAllProcsLPr(long now, boolean always, boolean memLowered) {
synchronized (mProfilerLock) {
if (!always) {
if (now < (mLastFullPssTime
@@ -870,10 +871,9 @@
for (int i = mPendingPssProfiles.size() - 1; i >= 0; i--) {
mPendingPssProfiles.get(i).abortNextPssTime();
}
- mPendingPssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLocked());
+ mPendingPssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLOSP());
mPendingPssProfiles.clear();
- for (int i = mService.mProcessList.getLruSizeLocked() - 1; i >= 0; i--) {
- ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
+ mService.mProcessList.forEachLruProcessesLOSP(false, app -> {
final ProcessProfileRecord profile = app.mProfile;
if (profile.getThread() == null
|| profile.getSetProcState() == PROCESS_STATE_NONEXISTENT) {
@@ -889,7 +889,7 @@
updateNextPssTimeLPf(profile.getSetProcState(), profile, now, true);
mPendingPssProfiles.add(profile);
}
- }
+ });
if (!mBgHandler.hasMessages(BgHandler.COLLECT_PSS_BG_MSG)) {
mBgHandler.sendEmptyMessage(BgHandler.COLLECT_PSS_BG_MSG);
}
@@ -897,12 +897,12 @@
}
void setTestPssMode(boolean enabled) {
- synchronized (mService) {
+ synchronized (mProcLock) {
mTestPssMode = enabled;
if (enabled) {
// Whenever we enable the mode, we want to take a snapshot all of current
// process mem use.
- requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, true);
+ requestPssAllProcsLPr(SystemClock.uptimeMillis(), true, true);
}
}
}
@@ -921,8 +921,8 @@
return mLastMemoryLevel <= ADJ_MEM_FACTOR_NORMAL;
}
- @GuardedBy("mService")
- void updateLowRamTimestampLocked(long now) {
+ @GuardedBy("mProcLock")
+ void updateLowRamTimestampLPr(long now) {
mLowRamTimeSinceLastIdle = 0;
if (mLowRamStartTime != 0) {
mLowRamStartTime = now;
@@ -939,9 +939,8 @@
mMemFactorOverride = factor;
}
- @GuardedBy("mService")
- boolean updateLowMemStateLocked(int numCached, int numEmpty, int numTrimming) {
- final int numOfLru = mService.mProcessList.getLruSizeLocked();
+ @GuardedBy({"mService", "mProcLock"})
+ boolean updateLowMemStateLSP(int numCached, int numEmpty, int numTrimming) {
final long now = SystemClock.uptimeMillis();
int memFactor;
if (mLowMemDetector != null && mLowMemDetector.isAvailable()) {
@@ -973,7 +972,7 @@
if (DEBUG_OOM_ADJ) {
Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor + " override=" + mMemFactorOverride
+ " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel
- + " numProcs=" + mService.mProcessList.getLruSizeLocked()
+ + " numProcs=" + mService.mProcessList.getLruSizeLOSP()
+ " last=" + mLastNumProcesses);
}
boolean override;
@@ -982,7 +981,7 @@
}
if (memFactor > mLastMemoryLevel) {
if (!override && (!mAllowLowerMemLevel
- || mService.mProcessList.getLruSizeLocked() >= mLastNumProcesses)) {
+ || mService.mProcessList.getLruSizeLOSP() >= mLastNumProcesses)) {
memFactor = mLastMemoryLevel;
if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!");
}
@@ -992,7 +991,7 @@
FrameworkStatsLog.write(FrameworkStatsLog.MEMORY_FACTOR_STATE_CHANGED, memFactor);
}
mLastMemoryLevel = memFactor;
- mLastNumProcesses = mService.mProcessList.getLruSizeLocked();
+ mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
boolean allChanged;
int trackerMemFactor;
synchronized (mService.mProcessStats.mLock) {
@@ -1004,7 +1003,6 @@
if (mLowRamStartTime == 0) {
mLowRamStartTime = now;
}
- int step = 0;
int fgTrimLevel;
switch (memFactor) {
case ADJ_MEM_FACTOR_CRITICAL:
@@ -1022,126 +1020,129 @@
if (mHasHomeProcess) minFactor++;
if (mHasPreviousProcess) minFactor++;
if (factor < minFactor) factor = minFactor;
- int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
- for (int i = 0; i < numOfLru; i++) {
- final ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
+ final int actualFactor = factor;
+ final int[] step = {0};
+ final int[] curLevel = {ComponentCallbacks2.TRIM_MEMORY_COMPLETE};
+ mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
final ProcessProfileRecord profile = app.mProfile;
final int trimMemoryLevel = profile.getTrimMemoryLevel();
- if (allChanged || app.procStateChanged) {
- mService.setProcessTrackerStateLocked(app, trackerMemFactor, now);
- app.procStateChanged = false;
+ final ProcessStateRecord state = app.mState;
+ final int curProcState = state.getCurProcState();
+ IApplicationThread thread;
+ if (allChanged || state.hasProcStateChanged()) {
+ mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
+ state.setProcStateChanged(false);
}
- if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
- && !app.killedByAm) {
- if (trimMemoryLevel < curLevel && app.thread != null) {
+ if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
+ if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) {
try {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
Slog.v(TAG_OOM_ADJ,
"Trimming memory of " + app.processName
- + " to " + curLevel);
+ + " to " + curLevel[0]);
}
- app.thread.scheduleTrimMemory(curLevel);
+ thread.scheduleTrimMemory(curLevel[0]);
} catch (RemoteException e) {
}
}
- profile.setTrimMemoryLevel(curLevel);
- step++;
- if (step >= factor) {
- step = 0;
- switch (curLevel) {
+ profile.setTrimMemoryLevel(curLevel[0]);
+ step[0]++;
+ if (step[0] >= actualFactor) {
+ step[0] = 0;
+ switch (curLevel[0]) {
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
- curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
+ curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
break;
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
- curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+ curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
break;
}
}
- } else if (app.getCurProcState() == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
- && !app.killedByAm) {
+ } else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+ && !app.isKilledByAm()) {
if (trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
- && app.thread != null) {
+ && (thread = app.getThread()) != null) {
try {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
Slog.v(TAG_OOM_ADJ,
"Trimming memory of heavy-weight " + app.processName
+ " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
}
- app.thread.scheduleTrimMemory(
+ thread.scheduleTrimMemory(
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
} catch (RemoteException e) {
}
}
profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
} else {
- if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- || app.systemNoUi) && profile.hasPendingUiClean()) {
+ if ((curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
// If this application is now in the background and it
// had done UI, then give it the special trim level to
// have it free UI resources.
final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
- if (trimMemoryLevel < level && app.thread != null) {
+ if (trimMemoryLevel < level && (thread = app.getThread()) != null) {
try {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
+ app.processName + " to " + level);
}
- app.thread.scheduleTrimMemory(level);
+ thread.scheduleTrimMemory(level);
} catch (RemoteException e) {
}
}
profile.setPendingUiClean(false);
}
- if (trimMemoryLevel < fgTrimLevel && app.thread != null) {
+ if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {
try {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName
+ " to " + fgTrimLevel);
}
- app.thread.scheduleTrimMemory(fgTrimLevel);
+ thread.scheduleTrimMemory(fgTrimLevel);
} catch (RemoteException e) {
}
}
profile.setTrimMemoryLevel(fgTrimLevel);
}
- }
+ });
} else {
if (mLowRamStartTime != 0) {
mLowRamTimeSinceLastIdle += now - mLowRamStartTime;
mLowRamStartTime = 0;
}
- for (int i = 0; i < numOfLru; i++) {
- final ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
+ mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
final ProcessProfileRecord profile = app.mProfile;
- if (allChanged || app.procStateChanged) {
- mService.setProcessTrackerStateLocked(app, trackerMemFactor, now);
- app.procStateChanged = false;
+ final IApplicationThread thread;
+ final ProcessStateRecord state = app.mState;
+ if (allChanged || state.hasProcStateChanged()) {
+ mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
+ state.setProcStateChanged(false);
}
- if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- || app.systemNoUi) && profile.hasPendingUiClean()) {
+ if ((state.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
if (profile.getTrimMemoryLevel() < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
- && app.thread != null) {
+ && (thread = app.getThread()) != null) {
try {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
Slog.v(TAG_OOM_ADJ,
"Trimming memory of ui hidden " + app.processName
+ " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
}
- app.thread.scheduleTrimMemory(
- ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+ thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
} catch (RemoteException e) {
}
}
profile.setPendingUiClean(false);
}
profile.setTrimMemoryLevel(0);
- }
+ });
}
return allChanged;
}
- @GuardedBy("mService")
- long getLowRamTimeSinceIdleLocked(long now) {
+ @GuardedBy("mProcLock")
+ long getLowRamTimeSinceIdleLPr(long now) {
return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0);
}
@@ -1262,7 +1263,7 @@
// If there are no longer any background processes running,
// and the app that died was not running instrumentation,
// then tell everyone we are now low on memory.
- if (!mService.mProcessList.haveBackgroundProcessLocked()) {
+ if (!mService.mProcessList.haveBackgroundProcessLOSP()) {
boolean doReport = Build.IS_DEBUGGABLE;
final long now = SystemClock.uptimeMillis();
if (doReport) {
@@ -1272,19 +1273,19 @@
mLastMemUsageReportTime = now;
}
}
- final int lruSize = mService.mProcessList.getLruSizeLocked();
+ final int lruSize = mService.mProcessList.getLruSizeLOSP();
final ArrayList<ProcessMemInfo> memInfos = doReport
? new ArrayList<ProcessMemInfo>(lruSize) : null;
EventLogTags.writeAmLowMemory(lruSize);
- for (int i = lruSize - 1; i >= 0; i--) {
- ProcessRecord rec = mService.mProcessList.mLruProcesses.get(i);
- if (rec == dyingProc || rec.thread == null) {
+ mService.mProcessList.forEachLruProcessesLOSP(false, rec -> {
+ if (rec == dyingProc || rec.getThread() == null) {
return;
}
+ final ProcessStateRecord state = rec.mState;
if (memInfos != null) {
- memInfos.add(new ProcessMemInfo(rec.processName, rec.pid,
- rec.setAdj, rec.setProcState,
- rec.adjType, rec.makeAdjReason()));
+ memInfos.add(new ProcessMemInfo(rec.processName, rec.getPid(),
+ state.getSetAdj(), state.getSetProcState(),
+ state.getAdjType(), state.makeAdjReason()));
}
final ProcessProfileRecord profile = rec.mProfile;
if ((profile.getLastLowMemory() + mService.mConstants.GC_MIN_INTERVAL) <= now) {
@@ -1292,7 +1293,7 @@
// state for a GC request. Make sure to do
// heavy/important/visible/foreground processes first.
synchronized (mProfilerLock) {
- if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+ if (state.getSetAdj() <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
profile.setLastRequestedGc(0);
} else {
profile.setLastRequestedGc(profile.getLastLowMemory());
@@ -1303,7 +1304,7 @@
addProcessToGcListLPf(rec);
}
}
- }
+ });
if (doReport) {
Message msg = mService.mHandler.obtainMessage(REPORT_MEM_USAGE_MSG, memInfos);
mService.mHandler.sendMessage(msg);
@@ -1554,7 +1555,9 @@
PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
String[] emptyArgs = new String[] { };
catPw.println();
- mService.mProcessList.dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null, -1);
+ synchronized (mProcLock) {
+ mService.mProcessList.dumpProcessesLSP(null, catPw, emptyArgs, 0, false, null, -1);
+ }
catPw.println();
mService.mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
false, null).dumpLocked();
@@ -1575,7 +1578,7 @@
}
}
- @GuardedBy("mService")
+ @GuardedBy("mProfilerLock")
private void stopProfilerLPf(ProcessRecord proc, int profileType) {
if (proc == null || proc == mProfileData.getProfileProc()) {
proc = mProfileData.getProfileProc();
@@ -1644,7 +1647,7 @@
}
mProfileData.getProfilerInfo().profileFd = null;
- if (proc.pid == mService.MY_PID) {
+ if (proc.getPid() == mService.MY_PID) {
// When profiling the system server itself, avoid closing the file
// descriptor, as profilerControl will not create a copy.
// Note: it is also not correct to just set profileFd to null, as the
@@ -1930,6 +1933,7 @@
AppProfiler(ActivityManagerService service, Looper bgLooper, LowMemDetector detector) {
mService = service;
+ mProcLock = service.mProcLock;
mBgHandler = new BgHandler(bgLooper);
mLowMemDetector = detector;
mProcessCpuThread = new ProcessCpuThread("CpuTracker");
@@ -2027,19 +2031,21 @@
i >= 0 && app.getActiveInstrumentation() == null; i--) {
ActiveInstrumentation aInstr = mService.mActiveInstrumentation.get(i);
if (!aInstr.mFinished && aInstr.mTargetInfo.uid == app.uid) {
- if (aInstr.mTargetProcesses.length == 0) {
- // This is the wildcard mode, where every process brought up for
- // the target instrumentation should be included.
- if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) {
- app.setActiveInstrumentation(aInstr);
- aInstr.mRunningProcesses.add(app);
- }
- } else {
- for (String proc : aInstr.mTargetProcesses) {
- if (proc.equals(app.processName)) {
+ synchronized (mProcLock) {
+ if (aInstr.mTargetProcesses.length == 0) {
+ // This is the wildcard mode, where every process brought up for
+ // the target instrumentation should be included.
+ if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) {
app.setActiveInstrumentation(aInstr);
aInstr.mRunningProcesses.add(app);
- break;
+ }
+ } else {
+ for (String proc : aInstr.mTargetProcesses) {
+ if (proc.equals(app.processName)) {
+ app.setActiveInstrumentation(aInstr);
+ aInstr.mRunningProcesses.add(app);
+ break;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b153cfd..c6947c2d 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -43,6 +43,7 @@
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;
@@ -96,8 +97,6 @@
return t;
});
- private final Context mContext;
-
@GuardedBy("mStats")
private final BatteryStatsImpl mStats;
@@ -168,15 +167,39 @@
@GuardedBy("this")
private long mLastCollectionTimeStamp;
+ final Injector mInjector;
+
+ @VisibleForTesting
+ public static class Injector {
+ private final Context mContext;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
+ public <T> T getSystemService(Class<T> serviceClass) {
+ return mContext.getSystemService(serviceClass);
+ }
+
+ public <T> T getLocalService(Class<T> serviceClass) {
+ return LocalServices.getService(serviceClass);
+ }
+ }
+
BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
- mContext = context;
+ this(new Injector(context), stats);
+ }
+
+ @VisibleForTesting
+ BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats) {
+ mInjector = injector;
mStats = stats;
}
public void systemServicesReady() {
- final WifiManager wm = mContext.getSystemService(WifiManager.class);
- final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- final PowerStatsInternal psi = LocalServices.getService(PowerStatsInternal.class);
+ final WifiManager wm = mInjector.getSystemService(WifiManager.class);
+ final TelephonyManager tm = mInjector.getSystemService(TelephonyManager.class);
+ final PowerStatsInternal psi = mInjector.getLocalService(PowerStatsInternal.class);
synchronized (mWorkerLock) {
mWifiManager = wm;
mTelephony = tm;
@@ -184,12 +207,13 @@
if (mPowerStatsInternal != null) {
populateEnergyConsumerSubsystemMapsLocked();
final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
- final boolean[] supportedBuckets = getSupportedEnergyBuckets(
- initialMeasuredEnergies);
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(supportedBuckets);
+ mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
}
}
}
@@ -717,15 +741,16 @@
/**
* Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
- * their corresponding {@link MeasuredEnergyStats.EnergyBucket}s.
+ * 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 energy bucket i is 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) {
return null;
}
- final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS];
+ 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)) {
@@ -747,7 +772,8 @@
* EnergyConsumerResult}[]
*/
@GuardedBy("mWorkerLock")
- private @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
+ @VisibleForTesting
+ public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
final EnergyConsumerResult[] results;
try {
results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
@@ -761,28 +787,41 @@
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[i] = subsystem;
- energyUJ[i] = consumer.energyUWs;
+ 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 size;
+ return arraySize;
}
};
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c287240..773e313 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -21,6 +21,10 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.State;
+import android.hardware.power.stats.StateResidency;
+import android.hardware.power.stats.StateResidencyResult;
import android.net.INetworkManagementEventObserver;
import android.net.NetworkCapabilities;
import android.os.BatteryStats;
@@ -51,6 +55,7 @@
import android.os.health.HealthStatsParceler;
import android.os.health.HealthStatsWriter;
import android.os.health.UidHealthStats;
+import android.power.PowerStatsInternal;
import android.provider.Settings;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
@@ -87,10 +92,13 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
/**
* All information we are collecting about things that can happen that impact
@@ -112,23 +120,23 @@
private final BatteryExternalStatsWorker mWorker;
private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
- private native void getLowPowerStats(RpmStats rpmStats);
- private native int getPlatformLowPowerStats(ByteBuffer outBuffer);
- private native int getSubsystemLowPowerStats(ByteBuffer outBuffer);
private native void getRailEnergyPowerStats(RailStats railStats);
private CharsetDecoder mDecoderStat = StandardCharsets.UTF_8
.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.replaceWith("?");
- private ByteBuffer mUtf8BufferStat = ByteBuffer.allocateDirect(MAX_LOW_POWER_STATS_SIZE);
- private CharBuffer mUtf16BufferStat = CharBuffer.allocate(MAX_LOW_POWER_STATS_SIZE);
private static final int MAX_LOW_POWER_STATS_SIZE = 4096;
+ private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Object mLock = new Object();
+ private PowerStatsInternal mPowerStatsInternal = null;
+ private Map<Integer, String> mEntityNames = new HashMap();
+ private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
+
@GuardedBy("mStats")
private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
@GuardedBy("mStats")
@@ -163,16 +171,57 @@
}
};
+ 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;
+ }
+
+ for (int i = 0; i < entities.length; i++) {
+ final PowerEntity entity = entities[i];
+ Map<Integer, String> states = new HashMap();
+ for (int j = 0; j < entity.states.length; j++) {
+ final State state = entity.states[j];
+ states.put(state.id, state.name);
+ }
+
+ mEntityNames.put(entity.id, entity.name);
+ mStateNames.put(entity.id, states);
+ }
+ }
+
/**
* Replaces the information in the given rpmStats with up-to-date information.
*/
@Override
public void fillLowPowerStats(RpmStats rpmStats) {
- if (DBG) Slog.d(TAG, "begin getLowPowerStats");
+ final StateResidencyResult[] results;
try {
- getLowPowerStats(rpmStats);
- } finally {
- if (DBG) Slog.d(TAG, "end getLowPowerStats");
+ 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;
+ }
+
+ for (int i = 0; i < results.length; i++) {
+ final StateResidencyResult result = results[i];
+ RpmStats.PowerStateSubsystem subsystem =
+ rpmStats.getSubsystem(mEntityNames.get(result.id));
+
+ for (int j = 0; j < result.stateResidencyData.length; j++) {
+ final StateResidency stateResidency = result.stateResidencyData[j];
+ subsystem.putState(mStateNames.get(result.id).get(stateResidency.id),
+ stateResidency.totalTimeInStateMs,
+ (int) stateResidency.totalStateEntryCount);
+ }
}
}
@@ -187,47 +236,46 @@
}
@Override
- public String getPlatformLowPowerStats() {
- if (DBG) Slog.d(TAG, "begin getPlatformLowPowerStats");
- try {
- mUtf8BufferStat.clear();
- mUtf16BufferStat.clear();
- mDecoderStat.reset();
- int bytesWritten = getPlatformLowPowerStats(mUtf8BufferStat);
- if (bytesWritten < 0) {
- return null;
- } else if (bytesWritten == 0) {
- return "Empty";
- }
- mUtf8BufferStat.limit(bytesWritten);
- mDecoderStat.decode(mUtf8BufferStat, mUtf16BufferStat, true);
- mUtf16BufferStat.flip();
- return mUtf16BufferStat.toString();
- } finally {
- if (DBG) Slog.d(TAG, "end getPlatformLowPowerStats");
- }
- }
-
- @Override
public String getSubsystemLowPowerStats() {
- if (DBG) Slog.d(TAG, "begin getSubsystemLowPowerStats");
+ final StateResidencyResult[] results;
try {
- mUtf8BufferStat.clear();
- mUtf16BufferStat.clear();
- mDecoderStat.reset();
- int bytesWritten = getSubsystemLowPowerStats(mUtf8BufferStat);
- if (bytesWritten < 0) {
- return null;
- } else if (bytesWritten == 0) {
- return "Empty";
- }
- mUtf8BufferStat.limit(bytesWritten);
- mDecoderStat.decode(mUtf8BufferStat, mUtf16BufferStat, true);
- mUtf16BufferStat.flip();
- return mUtf16BufferStat.toString();
- } finally {
- if (DBG) Slog.d(TAG, "end getSubsystemLowPowerStats");
+ 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";
}
+
+ if (results.length == 0) return "Empty";
+
+ int charsLeft = MAX_LOW_POWER_STATS_SIZE;
+ StringBuilder builder = new StringBuilder("SubsystemPowerState");
+ for (int i = 0; i < results.length; i++) {
+ final StateResidencyResult result = results[i];
+ StringBuilder subsystemBuilder = new StringBuilder();
+ subsystemBuilder.append(" subsystem_" + i);
+ subsystemBuilder.append(" name=" + mEntityNames.get(result.id));
+
+ for (int j = 0; j < result.stateResidencyData.length; j++) {
+ final StateResidency stateResidency = result.stateResidencyData[j];
+ subsystemBuilder.append(" state_" + j);
+ subsystemBuilder.append(" name=" + mStateNames.get(result.id).get(
+ stateResidency.id));
+ subsystemBuilder.append(" time=" + stateResidency.totalTimeInStateMs);
+ subsystemBuilder.append(" count=" + stateResidency.totalStateEntryCount);
+ subsystemBuilder.append(" last entry=" + stateResidency.lastEntryTimestampMs);
+ }
+
+ if (subsystemBuilder.length() <= charsLeft) {
+ charsLeft -= subsystemBuilder.length();
+ builder.append(subsystemBuilder);
+ } else {
+ Slog.e(TAG, "getSubsystemLowPowerStats: buffer not enough");
+ break;
+ }
+ }
+
+ return builder.toString();
}
BatteryStatsService(Context context, File systemDir, Handler handler) {
@@ -274,6 +322,11 @@
} catch (RemoteException e) {
Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
}
+ mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+ if (mPowerStatsInternal != null) {
+ populatePowerEntityMaps();
+ }
+
Watchdog.getInstance().addMonitor(this);
}
@@ -561,10 +614,10 @@
* Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
* and per-UID basis.
*/
- public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
+ public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
mContext.enforceCallingPermission(
android.Manifest.permission.BATTERY_STATS, null);
- return mBatteryUsageStatsProvider.getBatteryUsageStats(query);
+ return mBatteryUsageStatsProvider.getBatteryUsageStats(queries);
}
public byte[] getStatistics() {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 4de1218..7e65434 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -26,6 +26,7 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -42,6 +43,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerWhitelistManager.TempAllowListType;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -221,7 +223,7 @@
}
public boolean isPendingBroadcastProcessLocked(int pid) {
- return mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid;
+ return mPendingBroadcast != null && mPendingBroadcast.curApp.getPid() == pid;
}
public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
@@ -285,19 +287,21 @@
ProcessRecord app) throws RemoteException {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " for app " + app);
- if (app.thread == null) {
+ final IApplicationThread thread = app.getThread();
+ if (thread == null) {
throw new RemoteException();
}
- if (app.inFullBackup) {
+ if (app.isInFullBackup()) {
skipReceiverLocked(r);
return;
}
- r.receiver = app.thread.asBinder();
+ r.receiver = thread.asBinder();
r.curApp = app;
- app.curReceivers.add(r);
- app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
- mService.mProcessList.updateLruProcessLocked(app, false, null);
+ final ProcessReceiverRecord prr = app.mReceivers;
+ prr.addCurReceiver(r);
+ app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+ mService.updateLruProcessLocked(app, false, null);
// Make sure the oom adj score is updated before delivering the broadcast.
// Force an update, even if there are other pending requests, overall it still saves time,
// because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
@@ -314,10 +318,10 @@
+ ": " + r);
mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
+ thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
mService.compatibilityInfoForPackage(r.curReceiver.applicationInfo),
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
- app.getReportedProcState());
+ app.mState.getReportedProcState());
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " DELIVERED for app " + app);
started = true;
@@ -327,7 +331,7 @@
"Process cur broadcast " + r + ": NOT STARTED!");
r.receiver = null;
r.curApp = null;
- app.curReceivers.remove(r);
+ prr.removeCurReceiver(r);
}
}
}
@@ -335,7 +339,7 @@
public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
boolean didSomething = false;
final BroadcastRecord br = mPendingBroadcast;
- if (br != null && br.curApp.pid > 0 && br.curApp.pid == app.pid) {
+ if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
if (br.curApp != app) {
Slog.e(TAG, "App mismatch when sending pending broadcast to "
+ app.processName + ", intended target is " + br.curApp.processName);
@@ -362,7 +366,7 @@
public void skipPendingBroadcastLocked(int pid) {
final BroadcastRecord br = mPendingBroadcast;
- if (br != null && br.curApp.pid == pid) {
+ if (br != null && br.curApp.getPid() == pid) {
br.state = BroadcastRecord.IDLE;
br.nextReceiver = mPendingBroadcastRecvIndex;
mPendingBroadcast = null;
@@ -502,8 +506,8 @@
r.receiver = null;
r.intent.setComponent(null);
- if (r.curApp != null && r.curApp.curReceivers.contains(r)) {
- r.curApp.curReceivers.remove(r);
+ if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
+ r.curApp.mReceivers.removeCurReceiver(r);
mService.enqueueOomAdjTargetLocked(r.curApp);
}
if (r.curFilter != null) {
@@ -577,12 +581,14 @@
throws RemoteException {
// Send the intent to the receiver asynchronously using one-way binder calls.
if (app != null) {
- if (app.thread != null) {
+ final IApplicationThread thread = app.getThread();
+ if (thread != null) {
// If we have an app thread, do the call through that so it is
// correctly ordered with other one-way calls.
try {
- app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
- data, extras, ordered, sticky, sendingUser, app.getReportedProcState());
+ thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
+ data, extras, ordered, sticky, sendingUser,
+ app.mState.getReportedProcState());
// TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
// DeadObjectException when the process isn't actually dead.
//} catch (DeadObjectException ex) {
@@ -592,8 +598,8 @@
// Failed to call into the process. It's either dying or wedged. Kill it gently.
synchronized (mService) {
Slog.w(TAG, "Can't deliver broadcast to " + app.processName
- + " (pid " + app.pid + "). Crashing it.");
- app.scheduleCrash("can't deliver broadcast");
+ + " (pid " + app.getPid() + "). Crashing it.");
+ app.scheduleCrashLocked("can't deliver broadcast");
}
throw ex;
}
@@ -723,8 +729,8 @@
skip = true;
}
- if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed
- || filter.receiverList.app.isCrashing())) {
+ if (!skip && (filter.receiverList.app == null || filter.receiverList.app.isKilled()
+ || filter.receiverList.app.mErrorState.isCrashing())) {
Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
+ " to " + filter.receiverList + ": process gone or crashing");
skip = true;
@@ -793,7 +799,7 @@
// things that directly call the IActivityManager API, which
// are already core system stuff so don't matter for this.
r.curApp = filter.receiverList.app;
- filter.receiverList.app.curReceivers.add(r);
+ filter.receiverList.app.mReceivers.addCurReceiver(r);
mService.enqueueOomAdjTargetLocked(r.curApp);
mService.updateOomAdjPendingTargetsLocked(
OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
@@ -805,7 +811,7 @@
try {
if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
"Delivering to " + filter + " : " + r);
- if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {
+ if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
// Skip delivery if full backup in progress
// If it's an ordered broadcast, we need to continue to the next receiver.
if (ordered) {
@@ -832,7 +838,7 @@
if (filter.receiverList.app != null) {
filter.receiverList.app.removeAllowBackgroundActivityStartsToken(r);
if (ordered) {
- filter.receiverList.app.curReceivers.remove(r);
+ filter.receiverList.app.mReceivers.removeCurReceiver(r);
// Something wrong, its oom adj could be downgraded, but not in a hurry.
mService.enqueueOomAdjTargetLocked(r.curApp);
}
@@ -855,8 +861,8 @@
}
final boolean callerForeground = receiverRecord.callerApp != null
- ? receiverRecord.callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND
- : true;
+ ? receiverRecord.callerApp.mState.getSetSchedGroup()
+ != ProcessList.SCHED_GROUP_BACKGROUND : true;
// Show a permission review UI only for explicit broadcast from a foreground app
if (callerForeground && receiverRecord.intent.getComponent() != null) {
@@ -897,7 +903,7 @@
}
final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r,
- @BroadcastOptions.TempAllowListType int type) {
+ @TempAllowListType int type) {
if (duration > Integer.MAX_VALUE) {
duration = Integer.MAX_VALUE;
}
@@ -1017,16 +1023,16 @@
+ mPendingBroadcast.curApp);
boolean isDead;
- if (mPendingBroadcast.curApp.pid > 0) {
+ if (mPendingBroadcast.curApp.getPid() > 0) {
synchronized (mService.mPidsSelfLocked) {
ProcessRecord proc = mService.mPidsSelfLocked.get(
- mPendingBroadcast.curApp.pid);
- isDead = proc == null || proc.isCrashing();
+ mPendingBroadcast.curApp.getPid());
+ isDead = proc == null || proc.mErrorState.isCrashing();
}
} else {
- final ProcessRecord proc = mService.mProcessList.mProcessNames.get(
+ final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
- isDead = proc == null || !proc.pendingStart;
+ isDead = proc == null || !proc.isPendingStart();
}
if (!isDead) {
// It's still alive, so keep waiting
@@ -1496,7 +1502,7 @@
+ " (uid " + r.callingUid + ")");
skip = true;
}
- if (r.curApp != null && r.curApp.isCrashing()) {
+ if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
// If the target process is crashing, just skip it.
Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
+ " to " + r.curApp + ": process crashing");
@@ -1547,14 +1553,14 @@
info.activityInfo.applicationInfo.uid, false);
if (!skip) {
- final int allowed = mService.getAppStartModeLocked(
+ final int allowed = mService.getAppStartModeLOSP(
info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
// We won't allow this receiver to be launched if the app has been
// completely disabled from launches, or it was not explicitly sent
// to it and the app is in a state that should not receive it
- // (depending on how getAppStartModeLocked has determined that).
+ // (depending on how getAppStartModeLOSP has determined that).
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
Slog.w(TAG, "Background execution disabled: receiving "
+ r.intent + " to "
@@ -1629,7 +1635,7 @@
}
// Is this receiver's application already running?
- if (app != null && app.thread != null && !app.killed) {
+ if (app != null && app.getThread() != null && !app.isKilled()) {
try {
app.addPackage(info.activityInfo.packageName,
info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
@@ -1664,13 +1670,13 @@
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Need to start app ["
+ mQueueName + "] " + targetProcess + " for broadcast " + r);
- if ((r.curApp=mService.startProcessLocked(targetProcess,
+ r.curApp = mService.startProcessLocked(targetProcess,
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
- new HostingRecord("broadcast", r.curComponent),
- isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
- (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
- == null) {
+ new HostingRecord("broadcast", r.curComponent), isActivityCapable
+ ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
+ (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false);
+ if (r.curApp == null) {
// Ah, this recipient is unavailable. Finish it if necessary,
// and mark the broadcast record as ready for the next.
Slog.w(TAG, "Unable to launch app "
diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java
index 45ce4c5..50278fd 100644
--- a/services/core/java/com/android/server/am/CacheOomRanker.java
+++ b/services/core/java/com/android/server/am/CacheOomRanker.java
@@ -61,6 +61,7 @@
private final Object mPhenotypeFlagLock = new Object();
private final ActivityManagerService mService;
+ private final ActivityManagerGlobalLock mProcLock;
private final Object mProfilerLock;
@GuardedBy("mPhenotypeFlagLock")
@@ -106,6 +107,7 @@
CacheOomRanker(final ActivityManagerService service) {
mService = service;
+ mProcLock = service.mProcLock;
mProfilerLock = service.mAppProfiler.mProfilerLock;
}
@@ -179,15 +181,14 @@
* Re-rank the cached processes in the lru list with a weighted ordering
* of lru, rss size and number of times the process has been put in the cache.
*/
- public void reRankLruCachedApps(ProcessList processList) {
+ @GuardedBy({"mService", "mProcLock"})
+ void reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart) {
float lruWeight;
float usesWeight;
float rssWeight;
int[] lruPositions;
RankedProcessRecord[] scoredProcessRecords;
- ArrayList<ProcessRecord> lruList = processList.mLruProcesses;
-
synchronized (mPhenotypeFlagLock) {
lruWeight = mLruWeight;
usesWeight = mUsesWeight;
@@ -204,11 +205,11 @@
// Collect the least recently used processes to re-rank, only rank cached
// processes further down the list than mLruProcessServiceStart.
int cachedProcessPos = 0;
- for (int i = 0; i < processList.mLruProcessServiceStart
+ for (int i = 0; i < lruProcessServiceStart
&& cachedProcessPos < scoredProcessRecords.length; ++i) {
ProcessRecord app = lruList.get(i);
// Processes that will be assigned a cached oom adj score.
- if (!app.killedByAm && app.thread != null && app.curAdj
+ if (!app.isKilledByAm() && app.getThread() != null && app.mState.getCurAdj()
>= ProcessList.UNKNOWN_ADJ) {
scoredProcessRecords[cachedProcessPos].proc = app;
scoredProcessRecords[cachedProcessPos].score = 0.0f;
@@ -247,7 +248,8 @@
if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
boolean printedHeader = false;
for (int i = 0; i < scoredProcessRecords.length; ++i) {
- if (scoredProcessRecords[i].proc.pid != lruList.get(lruPositions[i]).pid) {
+ if (scoredProcessRecords[i].proc.getPid()
+ != lruList.get(lruPositions[i]).getPid()) {
if (!printedHeader) {
Slog.i(OomAdjuster.TAG, "reRankLruCachedApps");
printedHeader = true;
@@ -291,15 +293,15 @@
private static class LastActivityTimeComparator implements Comparator<RankedProcessRecord> {
@Override
public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
- return Long.compare(o1.proc.lastActivityTime, o2.proc.lastActivityTime);
+ return Long.compare(o1.proc.getLastActivityTime(), o2.proc.getLastActivityTime());
}
}
private static class CacheUseComparator implements Comparator<RankedProcessRecord> {
@Override
public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
- return Long.compare(o1.proc.getCacheOomRankerUseCount(),
- o2.proc.getCacheOomRankerUseCount());
+ return Long.compare(o1.proc.mState.getCacheOomRankerUseCount(),
+ o2.proc.mState.getCacheOomRankerUseCount());
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c558b3d..c18031f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -42,7 +42,6 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
-import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
@@ -153,9 +152,14 @@
*/
final ServiceThread mCachedAppOptimizerThread;
+ @GuardedBy("mProcLock")
private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
new ArrayList<ProcessRecord>();
+
private final ActivityManagerService mAm;
+
+ private final ActivityManagerGlobalLock mProcLock;
+
private final OnPropertiesChangedListener mOnFlagsChangedListener =
new OnPropertiesChangedListener() {
@Override
@@ -234,7 +238,7 @@
@VisibleForTesting
Handler mCompactionHandler;
private Handler mFreezeHandler;
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
private boolean mFreezerOverride = false;
// Maps process ID to last compaction statistics for processes that we've fully compacted. Used
@@ -242,6 +246,7 @@
// data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and
// facilitate removal of the oldest entry.
@VisibleForTesting
+ @GuardedBy("mProcLock")
LinkedHashMap<Integer, LastCompactionStats> mLastCompactionStats =
new LinkedHashMap<Integer, LastCompactionStats>() {
@Override
@@ -264,6 +269,7 @@
CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
ProcessDependencies processDependencies) {
mAm = am;
+ mProcLock = am.mProcLock;
mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
Process.THREAD_GROUP_SYSTEM, true);
mProcStateThrottle = new HashSet<>();
@@ -309,7 +315,7 @@
}
}
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
void dump(PrintWriter pw) {
pw.println("CachedAppOptimizer settings");
synchronized (mPhenotypeFlagLock) {
@@ -350,58 +356,57 @@
}
}
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
void compactAppSome(ProcessRecord app) {
- app.reqCompactAction = COMPACT_PROCESS_SOME;
+ app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+ COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
}
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
void compactAppFull(ProcessRecord app) {
- app.reqCompactAction = COMPACT_PROCESS_FULL;
+ app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+ COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
}
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
void compactAppPersistent(ProcessRecord app) {
- app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
+ app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+ COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
}
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
boolean shouldCompactPersistent(ProcessRecord app, long now) {
- return (app.lastCompactTime == 0
- || (now - app.lastCompactTime) > mCompactThrottlePersistent);
+ return (app.mOptRecord.getLastCompactTime() == 0
+ || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottlePersistent);
}
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
void compactAppBfgs(ProcessRecord app) {
- app.reqCompactAction = COMPACT_PROCESS_BFGS;
+ app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+ COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
}
- @GuardedBy("mAm")
+ @GuardedBy("mProcLock")
boolean shouldCompactBFGS(ProcessRecord app, long now) {
- return (app.lastCompactTime == 0
- || (now - app.lastCompactTime) > mCompactThrottleBFGS);
+ return (app.mOptRecord.getLastCompactTime() == 0
+ || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottleBFGS);
}
- @GuardedBy("mAm")
void compactAllSystem() {
- if (mUseCompaction) {
+ if (useCompaction()) {
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_SYSTEM_MSG));
}
@@ -468,29 +473,28 @@
// Override is applied immediately, restore is delayed
synchronized (mAm) {
- int processCount = mAm.mProcessList.mLruProcesses.size();
+ synchronized (mProcLock) {
+ mFreezerOverride = !enable;
+ Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);
- mFreezerOverride = !enable;
- Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);
+ mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {
+ if (process == null) {
+ return;
+ }
- for (int i = 0; i < processCount; i++) {
- ProcessRecord process = mAm.mProcessList.mLruProcesses.get(i);
+ final ProcessCachedOptimizerRecord opt = process.mOptRecord;
+ if (enable && opt.hasFreezerOverride()) {
+ freezeAppAsyncLSP(process);
+ opt.setFreezerOverride(false);
+ }
- if (process == null) {
- continue;
- }
+ if (!enable && opt.isFrozen()) {
+ unfreezeAppLSP(process);
- if (enable && process.freezerOverride) {
- freezeAppAsync(process);
- process.freezerOverride = false;
- }
-
- if (!enable && process.frozen) {
- unfreezeAppLocked(process);
-
- // Set freezerOverride *after* calling unfreezeAppLocked (it resets the flag)
- process.freezerOverride = true;
- }
+ // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
+ opt.setFreezerOverride(true);
+ }
+ });
}
}
@@ -740,19 +744,20 @@
}
// This will ensure app will be out of the freezer for at least FREEZE_TIMEOUT_MS
+ @GuardedBy("mAm")
void unfreezeTemporarily(ProcessRecord app) {
if (mUseFreezer) {
- synchronized (mAm) {
- if (app.frozen) {
- unfreezeAppLocked(app);
- freezeAppAsync(app);
+ synchronized (mProcLock) {
+ if (app.mOptRecord.isFrozen()) {
+ unfreezeAppLSP(app);
+ freezeAppAsyncLSP(app);
}
}
}
}
- @GuardedBy("mAm")
- void freezeAppAsync(ProcessRecord app) {
+ @GuardedBy({"mAm", "mProcLock"})
+ void freezeAppAsyncLSP(ProcessRecord app) {
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
mFreezeHandler.sendMessageDelayed(
@@ -761,16 +766,17 @@
FREEZE_TIMEOUT_MS);
}
- @GuardedBy("mAm")
- void unfreezeAppLocked(ProcessRecord app) {
+ @GuardedBy({"mAm", "mProcLock"})
+ void unfreezeAppLSP(ProcessRecord app) {
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
- app.freezerOverride = false;
-
- if (!app.frozen) {
+ final int pid = app.getPid();
+ final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+ opt.setFreezerOverride(false);
+ if (!opt.isFrozen()) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM,
- "Skipping unfreeze for process " + app.pid + " "
+ "Skipping unfreeze for process " + pid + " "
+ app.processName + " (not frozen)");
}
return;
@@ -781,25 +787,25 @@
boolean processKilled = false;
try {
- int freezeInfo = getBinderFreezeInfo(app.pid);
+ int freezeInfo = getBinderFreezeInfo(pid);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
- Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+ Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+ " received sync transactions while frozen, killing");
- app.kill("Sync transaction while in frozen state",
+ app.killLocked("Sync transaction while in frozen state",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
processKilled = true;
}
if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
- Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+ Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+ " received async transactions while frozen");
}
} catch (Exception e) {
- Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
+ Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "
+ app.processName + ". Killing it. Exception: " + e);
- app.kill("Unable to query binder frozen stats",
+ app.killLocked("Unable to query binder frozen stats",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
processKilled = true;
@@ -809,38 +815,38 @@
return;
}
- long freezeTime = app.freezeUnfreezeTime;
+ long freezeTime = opt.getFreezeUnfreezeTime();
try {
- freezeBinder(app.pid, false);
+ freezeBinder(pid, false);
} catch (RuntimeException e) {
- Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
+ Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
- app.kill("Unable to unfreeze",
+ app.killLocked("Unable to unfreeze",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
return;
}
try {
- Process.setProcessFrozen(app.pid, app.uid, false);
+ Process.setProcessFrozen(pid, app.uid, false);
- app.freezeUnfreezeTime = SystemClock.uptimeMillis();
- app.frozen = false;
+ opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
+ opt.setFrozen(false);
} catch (Exception e) {
- Slog.e(TAG_AM, "Unable to unfreeze " + app.pid + " " + app.processName
+ Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
+ ". This might cause inconsistency or UI hangs.");
}
- if (!app.frozen) {
+ if (!opt.isFrozen()) {
if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName);
+ Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
}
mFreezeHandler.sendMessage(
mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
- app.pid,
- (int) Math.min(app.freezeUnfreezeTime - freezeTime, Integer.MAX_VALUE),
+ pid,
+ (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
app.processName));
}
}
@@ -869,6 +875,7 @@
case COMPACT_PROCESS_MSG: {
long start = SystemClock.uptimeMillis();
ProcessRecord proc;
+ final ProcessCachedOptimizerRecord opt;
int pid;
String action;
final String name;
@@ -877,18 +884,19 @@
LastCompactionStats lastCompactionStats;
int lastOomAdj = msg.arg1;
int procState = msg.arg2;
- synchronized (mAm) {
+ synchronized (mProcLock) {
proc = mPendingCompactionProcesses.remove(0);
+ opt = proc.mOptRecord;
- pendingAction = proc.reqCompactAction;
- pid = proc.pid;
+ pendingAction = opt.getReqCompactAction();
+ pid = proc.getPid();
name = proc.processName;
// don't compact if the process has returned to perceptible
// and this is only a cached/home/prev compaction
if ((pendingAction == COMPACT_PROCESS_SOME
|| pendingAction == COMPACT_PROCESS_FULL)
- && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
+ && (proc.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"Skipping compaction as process " + name + " is "
@@ -897,8 +905,8 @@
return;
}
- lastCompactAction = proc.lastCompactAction;
- lastCompactTime = proc.lastCompactTime;
+ lastCompactAction = opt.getLastCompactAction();
+ lastCompactTime = opt.getLastCompactTime();
lastCompactionStats = mLastCompactionStats.get(pid);
}
@@ -1076,9 +1084,9 @@
lastOomAdj, ActivityManager.processStateAmToProto(procState),
zramFreeKbBefore, zramFreeKbAfter);
}
- synchronized (mAm) {
- proc.lastCompactTime = end;
- proc.lastCompactAction = pendingAction;
+ synchronized (mProcLock) {
+ opt.setLastCompactTime(end);
+ opt.setLastCompactAction(pendingAction);
}
if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
|| action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
@@ -1126,11 +1134,12 @@
}
}
- private void freezeProcess(ProcessRecord proc) {
- final int pid = proc.pid;
+ private void freezeProcess(final ProcessRecord proc) {
+ int pid = proc.getPid(); // Unlocked intentionally
final String name = proc.processName;
final long unfrozenDuration;
final boolean frozen;
+ final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
try {
// pre-check for locks to avoid unnecessary freeze/unfreeze operations
@@ -1146,26 +1155,27 @@
return;
}
- synchronized (mAm) {
- if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
- || proc.shouldNotFreeze) {
+ synchronized (mProcLock) {
+ pid = proc.getPid();
+ if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
+ || opt.shouldNotFreeze()) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Skipping freeze for process " + pid
- + " " + name + " curAdj = " + proc.curAdj
- + ", shouldNotFreeze = " + proc.shouldNotFreeze);
+ + " " + name + " curAdj = " + proc.mState.getCurAdj()
+ + ", shouldNotFreeze = " + opt.shouldNotFreeze());
}
return;
}
if (mFreezerOverride) {
- proc.freezerOverride = true;
+ opt.setFreezerOverride(true);
Slog.d(TAG_AM, "Skipping freeze for process " + pid
- + " " + name + " curAdj = " + proc.curAdj
+ + " " + name + " curAdj = " + proc.mState.getCurAdj()
+ "(override)");
return;
}
- if (pid == 0 || proc.frozen) {
+ if (pid == 0 || opt.isFrozen()) {
// Already frozen or not a real process, either one being
// launched or one being killed
return;
@@ -1177,24 +1187,28 @@
freezeBinder(pid, true);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
- proc.kill("Unable to freeze binder interface",
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+ mFreezeHandler.post(() -> {
+ synchronized (mAm) {
+ proc.killLocked("Unable to freeze binder interface",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+ }
+ });
}
- long unfreezeTime = proc.freezeUnfreezeTime;
+ long unfreezeTime = opt.getFreezeUnfreezeTime();
try {
Process.setProcessFrozen(pid, proc.uid, true);
- proc.freezeUnfreezeTime = SystemClock.uptimeMillis();
- proc.frozen = true;
+ opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
+ opt.setFrozen(true);
} catch (Exception e) {
Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
}
- unfrozenDuration = proc.freezeUnfreezeTime - unfreezeTime;
- frozen = proc.frozen;
+ unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;
+ frozen = opt.isFrozen();
}
if (!frozen) {
@@ -1225,13 +1239,17 @@
}
synchronized (mAm) {
- unfreezeAppLocked(proc);
+ synchronized (mProcLock) {
+ unfreezeAppLSP(proc);
+ }
}
}
} catch (IOException e) {
Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
synchronized (mAm) {
- unfreezeAppLocked(proc);
+ synchronized (mProcLock) {
+ unfreezeAppLSP(proc);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 478c512..f43c7f6 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -57,6 +57,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -148,7 +149,7 @@
ProcessRecord r = null;
if (caller != null) {
- r = mService.getRecordForAppLocked(caller);
+ r = mService.getRecordForAppLOSP(caller);
if (r == null) {
throw new SecurityException("Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid() + ") when getting content provider "
@@ -183,14 +184,14 @@
ProcessRecord dyingProc = null;
if (cpr != null && cpr.proc != null) {
- providerRunning = !cpr.proc.killed;
+ providerRunning = !cpr.proc.isKilled();
// Note if killedByAm is also set, this means the provider process has just been
// killed by AM (in ProcessRecord.kill()), but appDiedLocked() hasn't been called
// yet. So we need to call appDiedLocked() here and let it clean up.
// (See the commit message on I2c4ba1e87c2d47f2013befff10c49b3dc337a9a7 to see
// how to test this case.)
- if (cpr.proc.killed && cpr.proc.killedByAm) {
+ if (cpr.proc.isKilled() && cpr.proc.isKilledByAm()) {
Slog.wtf(TAG, cpr.proc.toString() + " was killed by AM but isn't really dead");
// Now we are going to wait for the death before starting the new process.
dyingProc = cpr.proc;
@@ -235,7 +236,7 @@
callingTag, stable, true, startTime, mService.mProcessList);
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
- final int verifiedAdj = cpr.proc.verifiedAdj;
+ final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
boolean success = mService.updateOomAdjLocked(cpr.proc, true,
OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
@@ -243,7 +244,7 @@
// it, we will check whether the process still exists. Note that this doesn't
// completely get rid of races with LMK killing the process, but should make
// them much smaller.
- if (success && verifiedAdj != cpr.proc.setAdj
+ if (success && verifiedAdj != cpr.proc.mState.getSetAdj()
&& !isProcessAliveLocked(cpr.proc)) {
success = false;
}
@@ -275,7 +276,7 @@
conn = null;
dyingProc = cpr.proc;
} else {
- cpr.proc.verifiedAdj = cpr.proc.setAdj;
+ cpr.proc.mState.setVerifiedAdj(cpr.proc.mState.getSetAdj());
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -428,15 +429,18 @@
checkTime(startTime, "getContentProviderImpl: looking for process record");
ProcessRecord proc = mService.getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
- if (proc != null && proc.thread != null && !proc.killed) {
+ IApplicationThread thread;
+ if (proc != null && (thread = proc.getThread()) != null
+ && !proc.isKilled()) {
if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
Slog.d(TAG, "Installing in existing process " + proc);
}
- if (!proc.pubProviders.containsKey(cpi.name)) {
+ final ProcessProviderRecord pr = proc.mProviders;
+ if (!pr.hasProvider(cpi.name)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
- proc.pubProviders.put(cpi.name, cpr);
+ pr.installProvider(cpi.name, cpr);
try {
- proc.thread.scheduleInstallProvider(cpi);
+ thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
@@ -445,8 +449,8 @@
proc = mService.startProcessLocked(
cpi.processName, cpr.appInfo, false, 0,
new HostingRecord("content provider",
- new ComponentName(
- cpi.applicationInfo.packageName, cpi.name)),
+ new ComponentName(
+ cpi.applicationInfo.packageName, cpi.name)),
Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false, false);
checkTime(startTime, "getContentProviderImpl: after start process");
if (proc == null) {
@@ -558,9 +562,9 @@
// Note we do it after releasing the lock.
String callerName = "unknown";
if (caller != null) {
- synchronized (mService) {
+ synchronized (mService.mProcLock) {
final ProcessRecord record =
- mService.mProcessList.getLRURecordForAppLocked(caller);
+ mService.mProcessList.getLRURecordForAppLOSP(caller);
if (record != null) {
callerName = record.processName;
}
@@ -600,7 +604,7 @@
mService.enforceNotIsolatedCaller("publishContentProviders");
synchronized (mService) {
- final ProcessRecord r = mService.getRecordForAppLocked(caller);
+ final ProcessRecord r = mService.getRecordForAppLOSP(caller);
if (DEBUG_MU) {
Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
}
@@ -617,7 +621,7 @@
if (src == null || src.info == null || src.provider == null) {
continue;
}
- ContentProviderRecord dst = r.pubProviders.get(src.info.name);
+ ContentProviderRecord dst = r.mProviders.getProvider(src.info.name);
if (dst == null) {
continue;
}
@@ -808,7 +812,7 @@
}
ProcessRecord proc = conn.provider.proc;
- if (proc == null || proc.thread == null) {
+ if (proc == null || proc.getThread() == null) {
// Seems like the process is already cleaned up.
return;
}
@@ -816,7 +820,7 @@
// As far as we're concerned, this is just like receiving a
// death notification... just a bit prematurely.
mService.reportUidInfoMessageLocked(TAG, "Process " + proc.processName
- + " (pid " + proc.pid + ") early provider death", proc.info.uid);
+ + " (pid " + proc.getPid() + ") early provider death", proc.info.uid);
final long token = Binder.clearCallingIdentity();
try {
mService.appDiedLocked(proc, "unstable content provider");
@@ -1074,7 +1078,8 @@
}
int numProviders = providers.size();
- app.pubProviders.ensureCapacity(numProviders + app.pubProviders.size());
+ final ProcessProviderRecord pr = app.mProviders;
+ pr.ensureProviderCapacity(numProviders + pr.numberOfProviders());
for (int i = 0; i < numProviders; i++) {
// NOTE: keep logic in sync with installEncryptionUnawareProviders
ProviderInfo cpi = providers.get(i);
@@ -1111,7 +1116,7 @@
if (DEBUG_MU) {
Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid);
}
- app.pubProviders.put(cpi.name, cpr);
+ pr.installProvider(cpi.name, cpr);
if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
// Don't add this if it is a platform component that is marked
// to run in multiple processes, because this is actually
@@ -1162,7 +1167,8 @@
public final void installSystemProviders() {
List<ProviderInfo> providers;
synchronized (mService) {
- ProcessRecord app = mService.mProcessList.mProcessNames.get("system", SYSTEM_UID);
+ ProcessRecord app = mService.mProcessList
+ .getProcessNamesLOSP().get("system", SYSTEM_UID);
providers = generateApplicationProvidersLocked(app);
if (providers != null) {
for (int i = providers.size() - 1; i >= 0; i--) {
@@ -1205,25 +1211,29 @@
final int matchFlags =
PackageManager.GET_PROVIDERS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- synchronized (mService) {
- final int numProc = mService.mProcessList.mProcessNames.getMap().size();
+ synchronized (mService.mProcLock) {
+ final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
+ mService.mProcessList.getProcessNamesLOSP().getMap();
+ final int numProc = pmap.size();
for (int iProc = 0; iProc < numProc; iProc++) {
- final SparseArray<ProcessRecord> apps =
- mService.mProcessList.mProcessNames.getMap().valueAt(iProc);
+ final SparseArray<ProcessRecord> apps = pmap.valueAt(iProc);
for (int iApp = 0, numApps = apps.size(); iApp < numApps; iApp++) {
final ProcessRecord app = apps.valueAt(iApp);
- if (app.userId != userId || app.thread == null || app.unlocked) continue;
+ if (app.userId != userId || app.getThread() == null || app.isUnlocked()) {
+ continue;
+ }
app.getPkgList().forEachPackage(pkgName -> {
try {
final PackageInfo pkgInfo = AppGlobals.getPackageManager()
- .getPackageInfo(pkgName, matchFlags, userId);
+ .getPackageInfo(pkgName, matchFlags, app.userId);
+ final IApplicationThread thread = app.getThread();
if (pkgInfo != null && !ArrayUtils.isEmpty(pkgInfo.providers)) {
for (ProviderInfo pi : pkgInfo.providers) {
// NOTE: keep in sync with generateApplicationProvidersLocked
final boolean processMatch =
Objects.equals(pi.processName, app.processName)
- || pi.multiprocess;
+ || pi.multiprocess;
final boolean userMatch = !mService.isSingleton(
pi.processName, pi.applicationInfo, pi.name, pi.flags)
|| app.userId == UserHandle.USER_SYSTEM;
@@ -1234,7 +1244,7 @@
if (processMatch && userMatch
&& (!isInstantApp || splitInstalled)) {
Log.v(TAG, "Installing " + pi);
- app.thread.scheduleInstallProvider(pi);
+ thread.scheduleInstallProvider(pi);
} else {
Log.v(TAG, "Skipping " + pi);
}
@@ -1259,8 +1269,9 @@
}
- for (int i = 0, size = r.conProviders.size(); i < size; i++) {
- ContentProviderConnection conn = r.conProviders.get(i);
+ final ProcessProviderRecord pr = r.mProviders;
+ for (int i = 0, size = pr.numberOfProviderConnections(); i < size; i++) {
+ ContentProviderConnection conn = pr.getProviderConnectionAt(i);
if (conn.provider == cpr) {
conn.incrementCount(stable);
return conn;
@@ -1272,10 +1283,11 @@
conn.startAssociationIfNeeded();
conn.initializeCount(stable);
cpr.connections.add(conn);
- r.conProviders.add(conn);
- mService.startAssociationLocked(r.uid, r.processName, r.getCurProcState(),
+ pr.addProviderConnection(conn);
+ mService.startAssociationLocked(r.uid, r.processName, r.mState.getCurProcState(),
cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
- if (updateLru && cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+ if (updateLru && cpr.proc != null
+ && r.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
// If this is a perceptible app accessing the provider, make
// sure to count it as being accessed and thus back up on
// the LRU list. This is good because content providers are
@@ -1325,13 +1337,14 @@
final ContentProviderRecord cpr = conn.provider;
conn.stopAssociation();
cpr.connections.remove(conn);
- conn.client.conProviders.remove(conn);
- if (conn.client.setProcState < ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+ conn.client.mProviders.removeProviderConnection(conn);
+ if (conn.client.mState.getSetProcState()
+ < ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
// The client is more important than last activity -- note the time this
// is happening, so we keep the old provider process around a bit as last
// activity to avoid thrashing it.
if (cpr.proc != null) {
- cpr.proc.lastProviderTime = SystemClock.uptimeMillis();
+ cpr.proc.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
}
}
mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
@@ -1429,9 +1442,9 @@
return mService.validateAssociationAllowedLocked(cpi.packageName,
cpi.applicationInfo.uid, null, callingUid) ? null : "<null>";
}
- final String r = callingApp.getPkgList().forEachPackage(pkgName -> {
+ final String r = callingApp.getPkgList().searchEachPackage(pkgName -> {
if (!mService.validateAssociationAllowedLocked(pkgName,
- callingApp.uid, cpi.packageName, cpi.applicationInfo.uid)) {
+ callingApp.uid, cpi.packageName, cpi.applicationInfo.uid)) {
return cpi.packageName;
}
return null;
@@ -1455,8 +1468,8 @@
private void maybeUpdateProviderUsageStatsLocked(ProcessRecord app, String providerPkgName,
String authority) {
- if (app == null
- || app.getCurProcState() > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ if (app == null || app.mState.getCurProcState()
+ > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
return;
}
@@ -1483,31 +1496,30 @@
private final long[] mProcessStateStatsLongs = new long[1];
private boolean isProcessAliveLocked(ProcessRecord proc) {
- if (proc.pid <= 0) {
+ final int pid = proc.getPid();
+ if (pid <= 0) {
if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
Slog.d(ActivityManagerService.TAG, "Process hasn't started yet: " + proc);
}
return false;
}
- if (proc.procStatFile == null) {
- proc.procStatFile = "/proc/" + proc.pid + "/stat";
- }
+ final String procStatFile = "/proc/" + pid + "/stat";
mProcessStateStatsLongs[0] = 0;
- if (!Process.readProcFile(proc.procStatFile, PROCESS_STATE_STATS_FORMAT, null,
+ if (!Process.readProcFile(procStatFile, PROCESS_STATE_STATS_FORMAT, null,
mProcessStateStatsLongs, null)) {
if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
Slog.d(ActivityManagerService.TAG,
- "UNABLE TO RETRIEVE STATE FOR " + proc.procStatFile);
+ "UNABLE TO RETRIEVE STATE FOR " + procStatFile);
}
return false;
}
final long state = mProcessStateStatsLongs[0];
if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
Slog.d(ActivityManagerService.TAG,
- "RETRIEVED STATE FOR " + proc.procStatFile + ": " + (char) state);
+ "RETRIEVED STATE FOR " + procStatFile + ": " + (char) state);
}
if (state != 'Z' && state != 'X' && state != 'x' && state != 'K') {
- return Process.getUidForPid(proc.pid) == proc.uid;
+ return Process.getUidForPid(pid) == proc.uid;
}
return false;
}
@@ -1537,7 +1549,7 @@
}
final boolean callerForeground = r == null
- || r.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
+ || r.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_BACKGROUND;
// Show a permission review UI only for starting from a foreground app
if (!callerForeground) {
@@ -1611,26 +1623,29 @@
}
}
ProcessRecord capp = conn.client;
+ final IApplicationThread thread = capp.getThread();
conn.dead = true;
if (conn.stableCount() > 0) {
- if (!capp.isPersistent() && capp.thread != null
- && capp.pid != 0 && capp.pid != ActivityManagerService.MY_PID) {
- capp.kill("depends on provider " + cpr.name.flattenToShortString()
- + " in dying proc " + (proc != null ? proc.processName : "??")
- + " (adj " + (proc != null ? proc.setAdj : "??") + ")",
+ final int pid = capp.getPid();
+ if (!capp.isPersistent() && thread != null
+ && pid != 0 && pid != ActivityManagerService.MY_PID) {
+ capp.killLocked(
+ "depends on provider " + cpr.name.flattenToShortString()
+ + " in dying proc " + (proc != null ? proc.processName : "??")
+ + " (adj " + (proc != null ? proc.mState.getSetAdj() : "??") + ")",
ApplicationExitInfo.REASON_DEPENDENCY_DIED,
ApplicationExitInfo.SUBREASON_UNKNOWN,
true);
}
- } else if (capp.thread != null && conn.provider.provider != null) {
+ } else if (thread != null && conn.provider.provider != null) {
try {
- capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
+ thread.unstableProviderDied(conn.provider.provider.asBinder());
} catch (RemoteException e) {
}
// In the protocol here, we don't expect the client to correctly
// clean up this connection, we'll just remove it.
cpr.connections.remove(i);
- if (conn.client.conProviders.remove(conn)) {
+ if (conn.client.mProviders.removeProviderConnection(conn)) {
mService.stopAssociationLocked(capp.uid, capp.processName,
cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
}
@@ -1671,7 +1686,7 @@
// It's being launched but we've reached maximum attempts, mark it as bad
alwaysBad = true;
}
- if (!alwaysBad && !app.bad && cpr.hasConnectionOrHandle()) {
+ if (!alwaysBad && !app.mErrorState.isBad() && cpr.hasConnectionOrHandle()) {
restart = true;
} else {
removeDyingProviderLocked(app, cpr, true);
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index 59a1939..b217cae 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -19,6 +19,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import android.app.ContentProviderHolder;
+import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.IContentProvider;
import android.content.pm.ApplicationInfo;
@@ -211,9 +212,10 @@
+ " caller=" + client);
}
}
- if (client.thread != null) {
+ final IApplicationThread thread = client.getThread();
+ if (thread != null) {
try {
- client.thread.notifyContentProviderPublishStatus(
+ thread.notifyContentProviderPublishStatus(
newHolder(status ? conn : null, false),
info.authority, userId, status);
} catch (RemoteException e) {
@@ -343,8 +345,8 @@
&& mAssociation == null && provider.proc != null
&& (provider.appInfo.uid != mOwningUid
|| !provider.info.processName.equals(mOwningProcessName))) {
- ProcessStats.ProcessStateHolder holder = provider.proc.getPkgList().get(
- provider.name.getPackageName());
+ ProcessStats.ProcessStateHolder holder =
+ provider.proc.getPkgList().get(provider.name.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced provider "
+ provider.name.toShortString() + ": proc=" + provider.proc);
@@ -373,7 +375,7 @@
if (hasExternalProcessHandles() &&
externalProcessTokenToHandle.get(mToken) != null) {
removeExternalProcessHandleInternalLocked(mToken);
- }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ErrorDialogController.java b/services/core/java/com/android/server/am/ErrorDialogController.java
new file mode 100644
index 0000000..ef135d5
--- /dev/null
+++ b/services/core/java/com/android/server/am/ErrorDialogController.java
@@ -0,0 +1,251 @@
+/*
+ * 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.am;
+
+import android.app.Dialog;
+import android.content.Context;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A controller to generate error dialogs in {@link ProcessRecord}.
+ */
+final class ErrorDialogController {
+ private final ProcessRecord mApp;
+ private final ActivityManagerService mService;
+ private final ActivityManagerGlobalLock mProcLock;
+
+ /**
+ * Dialogs being displayed due to crash.
+ */
+ @GuardedBy("mProcLock")
+ private List<AppErrorDialog> mCrashDialogs;
+
+ /**
+ * Dialogs being displayed due to app not responding.
+ */
+ @GuardedBy("mProcLock")
+ private List<AppNotRespondingDialog> mAnrDialogs;
+
+ /**
+ * Dialogs displayed due to strict mode violation.
+ */
+ @GuardedBy("mProcLock")
+ private List<StrictModeViolationDialog> mViolationDialogs;
+
+ /**
+ * Current wait for debugger dialog.
+ */
+ @GuardedBy("mProcLock")
+ private AppWaitingForDebuggerDialog mWaitDialog;
+
+ @GuardedBy("mProcLock")
+ boolean hasCrashDialogs() {
+ return mCrashDialogs != null;
+ }
+
+ @GuardedBy("mProcLock")
+ List<AppErrorDialog> getCrashDialogs() {
+ return mCrashDialogs;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean hasAnrDialogs() {
+ return mAnrDialogs != null;
+ }
+
+ @GuardedBy("mProcLock")
+ List<AppNotRespondingDialog> getAnrDialogs() {
+ return mAnrDialogs;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean hasViolationDialogs() {
+ return mViolationDialogs != null;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean hasDebugWaitingDialog() {
+ return mWaitDialog != null;
+ }
+
+ @GuardedBy("mProcLock")
+ void clearAllErrorDialogs() {
+ clearCrashDialogs();
+ clearAnrDialogs();
+ clearViolationDialogs();
+ clearWaitingDialog();
+ }
+
+ @GuardedBy("mProcLock")
+ void clearCrashDialogs() {
+ clearCrashDialogs(true /* needDismiss */);
+ }
+
+ @GuardedBy("mProcLock")
+ void clearCrashDialogs(boolean needDismiss) {
+ if (mCrashDialogs == null) {
+ return;
+ }
+ if (needDismiss) {
+ forAllDialogs(mCrashDialogs, Dialog::dismiss);
+ }
+ mCrashDialogs = null;
+ }
+
+ @GuardedBy("mProcLock")
+ void clearAnrDialogs() {
+ if (mAnrDialogs == null) {
+ return;
+ }
+ forAllDialogs(mAnrDialogs, Dialog::dismiss);
+ mAnrDialogs = null;
+ }
+
+ @GuardedBy("mProcLock")
+ void clearViolationDialogs() {
+ if (mViolationDialogs == null) {
+ return;
+ }
+ forAllDialogs(mViolationDialogs, Dialog::dismiss);
+ mViolationDialogs = null;
+ }
+
+ @GuardedBy("mProcLock")
+ void clearWaitingDialog() {
+ if (mWaitDialog == null) {
+ return;
+ }
+ mWaitDialog.dismiss();
+ mWaitDialog = null;
+ }
+
+ void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) {
+ for (int i = dialogs.size() - 1; i >= 0; i--) {
+ c.accept(dialogs.get(i));
+ }
+ }
+
+ @GuardedBy("mProcLock")
+ void showCrashDialogs(AppErrorDialog.Data data) {
+ List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
+ mCrashDialogs = new ArrayList<>();
+ for (int i = contexts.size() - 1; i >= 0; i--) {
+ final Context c = contexts.get(i);
+ mCrashDialogs.add(new AppErrorDialog(c, mService, data));
+ }
+ mService.mUiHandler.post(() -> {
+ List<AppErrorDialog> dialogs;
+ synchronized (mProcLock) {
+ dialogs = mCrashDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
+ }
+
+ @GuardedBy("mProcLock")
+ void showAnrDialogs(AppNotRespondingDialog.Data data) {
+ List<Context> contexts = getDisplayContexts(
+ mApp.mErrorState.isSilentAnr() /* lastUsedOnly */);
+ mAnrDialogs = new ArrayList<>();
+ for (int i = contexts.size() - 1; i >= 0; i--) {
+ final Context c = contexts.get(i);
+ mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
+ }
+ mService.mUiHandler.post(() -> {
+ List<AppNotRespondingDialog> dialogs;
+ synchronized (mProcLock) {
+ dialogs = mAnrDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
+ }
+
+ @GuardedBy("mProcLock")
+ void showViolationDialogs(AppErrorResult res) {
+ List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
+ mViolationDialogs = new ArrayList<>();
+ for (int i = contexts.size() - 1; i >= 0; i--) {
+ final Context c = contexts.get(i);
+ mViolationDialogs.add(
+ new StrictModeViolationDialog(c, mService, res, mApp));
+ }
+ mService.mUiHandler.post(() -> {
+ List<StrictModeViolationDialog> dialogs;
+ synchronized (mProcLock) {
+ dialogs = mViolationDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
+ }
+
+ @GuardedBy("mProcLock")
+ void showDebugWaitingDialogs() {
+ List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */);
+ final Context c = contexts.get(0);
+ mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, mApp);
+
+ mService.mUiHandler.post(() -> {
+ Dialog dialog;
+ synchronized (mProcLock) {
+ dialog = mWaitDialog;
+ }
+ if (dialog != null) {
+ dialog.show();
+ }
+ });
+ }
+
+ /**
+ * Helper function to collect contexts from crashed app located displays.
+ *
+ * @param lastUsedOnly Sets to {@code true} to indicate to only get last used context.
+ * Sets to {@code false} to collect contexts from crashed app located
+ * displays.
+ *
+ * @return display context list.
+ */
+ private List<Context> getDisplayContexts(boolean lastUsedOnly) {
+ List<Context> displayContexts = new ArrayList<>();
+ if (!lastUsedOnly) {
+ mApp.getWindowProcessController().getDisplayContextsWithErrorDialogs(displayContexts);
+ }
+ // If there is no foreground window display, fallback to last used display.
+ if (displayContexts.isEmpty() || lastUsedOnly) {
+ displayContexts.add(mService.mWmInternal != null
+ ? mService.mWmInternal.getTopFocusedDisplayUiContext()
+ : mService.mUiContext);
+ }
+ return displayContexts;
+ }
+
+ ErrorDialogController(ProcessRecord app) {
+ mApp = app;
+ mService = app.mService;
+ mProcLock = mService.mProcLock;
+ }
+}
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index 6e42aee..94eb076 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -16,6 +16,12 @@
package com.android.server.am;
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVTIMEO;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
import android.app.ApplicationErrorReport.CrashInfo;
import android.system.ErrnoException;
import android.system.Os;
@@ -23,8 +29,6 @@
import android.system.UnixSocketAddress;
import android.util.Slog;
-import static android.system.OsConstants.*;
-
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
@@ -259,8 +263,10 @@
// even though the process will vanish as soon as we let
// debuggerd proceed.
synchronized (mAm) {
- pr.setCrashing(true);
- pr.forceCrashReport = true;
+ synchronized (mAm.mProcLock) {
+ pr.mErrorState.setCrashing(true);
+ pr.mErrorState.setForceCrashReport(true);
+ }
}
// Crash reporting is synchronous but we want to let debuggerd
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5e146e1..d79fb8a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -51,6 +51,7 @@
import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
@@ -99,6 +100,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.IPlatformCompat;
@@ -205,11 +207,12 @@
int mNumCachedHiddenProcs = 0;
/** Track all uids that have actively running processes. */
+ @CompositeRWLock({"mService", "mProcLock"})
ActiveUids mActiveUids;
/**
* The handler to execute {@link #setProcessGroup} (it may be heavy if the process has many
- * threads) for reducing the time spent in {@link #applyOomAdjLocked}.
+ * threads) for reducing the time spent in {@link #applyOomAdjLSP}.
*/
private final Handler mProcessGroupHandler;
@@ -217,6 +220,7 @@
private final ActivityManagerService mService;
private final ProcessList mProcessList;
+ private final ActivityManagerGlobalLock mProcLock;
private final int mNumSlots;
private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
@@ -332,6 +336,7 @@
ServiceThread adjusterThread) {
mService = service;
mProcessList = processList;
+ mProcLock = service.mProcLock;
mActiveUids = activeUids;
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
@@ -388,23 +393,27 @@
@VisibleForTesting
@GuardedBy("mService")
void handleUserSwitchedLocked() {
+ mProcessList.forEachLruProcessesLOSP(false,
+ this::updateKeepWarmIfNecessaryForProcessLocked);
+ }
+
+ @GuardedBy("mService")
+ private void updateKeepWarmIfNecessaryForProcessLocked(final ProcessRecord app) {
final ArraySet<ComponentName> warmServices = mService.mConstants.KEEP_WARMING_SERVICES;
- final ArrayList<ProcessRecord> processes = mProcessList.mLruProcesses;
- for (int i = processes.size() - 1; i >= 0; i--) {
- final ProcessRecord app = processes.get(i);
- boolean includeWarmPkg = false;
- for (int j = warmServices.size() - 1; j >= 0; j--) {
- if (app.getPkgList().containsKey(warmServices.valueAt(j).getPackageName())) {
- includeWarmPkg = true;
- break;
- }
+ boolean includeWarmPkg = false;
+ final PackageList pkgList = app.getPkgList();
+ for (int j = warmServices.size() - 1; j >= 0; j--) {
+ if (pkgList.containsKey(warmServices.valueAt(j).getPackageName())) {
+ includeWarmPkg = true;
+ break;
}
- if (!includeWarmPkg) {
- continue;
- }
- for (int j = app.numberOfRunningServices() - 1; j >= 0; j--) {
- app.getRunningServiceAt(j).updateKeepWarmLocked();
- }
+ }
+ if (!includeWarmPkg) {
+ return;
+ }
+ final ProcessServiceRecord psr = app.mServices;
+ for (int j = psr.numberOfRunningServices() - 1; j >= 0; j--) {
+ psr.getRunningServiceAt(j).updateKeepWarmLocked();
}
}
@@ -419,11 +428,20 @@
@GuardedBy("mService")
boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll,
String oomAdjReason) {
+ synchronized (mProcLock) {
+ return updateOomAdjLSP(app, oomAdjAll, oomAdjReason);
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private boolean updateOomAdjLSP(ProcessRecord app, boolean oomAdjAll,
+ String oomAdjReason) {
if (oomAdjAll && mConstants.OOMADJ_UPDATE_QUICK) {
- return updateOomAdjLocked(app, oomAdjReason);
+ return updateOomAdjLSP(app, oomAdjReason);
}
final ProcessRecord topApp = mService.getTopApp();
- final boolean wasCached = app.isCached();
+ final ProcessStateRecord state = app.mState;
+ final boolean wasCached = state.isCached();
mAdjSeq++;
@@ -431,30 +449,31 @@
// If our app is currently cached, we know it, and that is it. Otherwise,
// we don't know it yet, and it needs to now be cached we will then
// need to do a complete oom adj.
- final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
- ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
+ final int cachedAdj = state.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
+ ? state.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
// Check if this process is in the pending list too, remove from pending list if so.
mPendingProcessSet.remove(app);
- boolean success = updateOomAdjLocked(app, cachedAdj, topApp, false,
+ boolean success = updateOomAdjLSP(app, cachedAdj, topApp, false,
SystemClock.uptimeMillis());
if (oomAdjAll
- && (wasCached != app.isCached() || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
+ && (wasCached != state.isCached()
+ || state.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
// Changed to/from cached state, so apps after it in the LRU
// list may also be changed.
- updateOomAdjLocked(oomAdjReason);
+ updateOomAdjLSP(oomAdjReason);
}
return success;
}
- @GuardedBy("mService")
- private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
+ @GuardedBy({"mService", "mProcLock"})
+ private boolean updateOomAdjLSP(ProcessRecord app, int cachedAdj,
ProcessRecord TOP_APP, boolean doingAll, long now) {
- if (app.thread == null) {
+ if (app.getThread() == null) {
return false;
}
- app.resetCachedInfo();
- UidRecord uidRec = app.uidRecord;
+ app.mState.resetCachedInfo();
+ UidRecord uidRec = app.getUidRecord();
if (uidRec != null) {
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
@@ -465,35 +484,23 @@
// Check if this process is in the pending list too, remove from pending list if so.
mPendingProcessSet.remove(app);
- computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false, true);
+ computeOomAdjLSP(app, cachedAdj, TOP_APP, doingAll, now, false, true);
- boolean success = applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
+ boolean success = applyOomAdjLSP(app, doingAll, now, SystemClock.elapsedRealtime());
if (uidRec != null) {
- // After uidRec.reset() above, for UidRecord that has multiple processes (ProcessRecord)
- // , We need to apply all ProcessRecord into UidRecord.
- final ArraySet<ProcessRecord> procRecords = app.uidRecord.procRecords;
- for (int i = procRecords.size() - 1; i >= 0; i--) {
- final ProcessRecord pr = procRecords.valueAt(i);
- if (!pr.killedByAm && pr.thread != null) {
- if (pr.isolated && pr.numberOfRunningServices() <= 0
- && pr.isolatedEntryPoint == null) {
- // No op.
- } else {
- // Keeping this process, update its uid.
- updateAppUidRecLocked(pr);
- }
- }
- }
+ // After uidRec.reset() above, for UidRecord with multiple processes (ProcessRecord),
+ // we need to apply all ProcessRecord into UidRecord.
+ uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
- && (uidRec.setProcState != uidRec.getCurProcState()
- || uidRec.setCapability != uidRec.curCapability
- || uidRec.mSetAllowlist != uidRec.mCurAllowlist)) {
+ && (uidRec.getSetProcState() != uidRec.getCurProcState()
+ || uidRec.getSetCapability() != uidRec.getCurCapability()
+ || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
ActiveUids uids = mTmpUidRecords;
uids.clear();
- uids.put(uidRec.uid, uidRec);
- updateUidsLocked(uids, SystemClock.elapsedRealtime());
- mProcessList.incrementProcStateSeqAndNotifyAppsLocked(uids);
+ uids.put(uidRec.getUid(), uidRec);
+ updateUidsLSP(uids, SystemClock.elapsedRealtime());
+ mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(uids);
}
}
@@ -505,11 +512,18 @@
*/
@GuardedBy("mService")
void updateOomAdjLocked(String oomAdjReason) {
+ synchronized (mProcLock) {
+ updateOomAdjLSP(oomAdjReason);
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateOomAdjLSP(String oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
// Clear any pending ones because we are doing a full update now.
mPendingProcessSet.clear();
mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
- updateOomAdjLockedInner(oomAdjReason, topApp , null, null, true, true);
+ updateOomAdjInnerLSP(oomAdjReason, topApp , null, null, true, true);
}
/**
@@ -522,8 +536,15 @@
*/
@GuardedBy("mService")
boolean updateOomAdjLocked(ProcessRecord app, String oomAdjReason) {
+ synchronized (mProcLock) {
+ return updateOomAdjLSP(app, oomAdjReason);
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private boolean updateOomAdjLSP(ProcessRecord app, String oomAdjReason) {
if (app == null || !mConstants.OOMADJ_UPDATE_QUICK) {
- updateOomAdjLocked(oomAdjReason);
+ updateOomAdjLSP(oomAdjReason);
return true;
}
@@ -534,20 +555,23 @@
mAdjSeq++;
// Firstly, try to see if the importance of itself gets changed
- final boolean wasCached = app.isCached();
- final int oldAdj = app.getCurRawAdj();
+ final ProcessStateRecord state = app.mState;
+ final boolean wasCached = state.isCached();
+ final int oldAdj = state.getCurRawAdj();
final int cachedAdj = oldAdj >= ProcessList.CACHED_APP_MIN_ADJ
? oldAdj : ProcessList.UNKNOWN_ADJ;
- final boolean wasBackground = ActivityManager.isProcStateBackground(app.setProcState);
- app.containsCycle = false;
- app.procStateChanged = false;
- app.resetCachedInfo();
+ final boolean wasBackground = ActivityManager.isProcStateBackground(
+ state.getSetProcState());
+ state.setContainsCycle(false);
+ state.setProcStateChanged(false);
+ state.resetCachedInfo();
// Check if this process is in the pending list too, remove from pending list if so.
mPendingProcessSet.remove(app);
- boolean success = updateOomAdjLocked(app, cachedAdj, topApp, false,
+ boolean success = updateOomAdjLSP(app, cachedAdj, topApp, false,
SystemClock.uptimeMillis());
- if (!success || (wasCached == app.isCached() && oldAdj != ProcessList.INVALID_ADJ
- && wasBackground == ActivityManager.isProcStateBackground(app.setProcState))) {
+ if (!success || (wasCached == state.isCached() && oldAdj != ProcessList.INVALID_ADJ
+ && wasBackground == ActivityManager.isProcStateBackground(
+ state.getSetProcState()))) {
// Okay, it's unchanged, it won't impact any service it binds to, we're done here.
if (DEBUG_OOM_ADJ) {
Slog.i(TAG_OOM_ADJ, "No oomadj changes for " + app);
@@ -569,23 +593,25 @@
// Track if any of them reachables could include a cycle
boolean containsCycle = false;
// Scan downstreams of the process record
- app.mReachable = true;
+ state.setReachable(true);
for (ProcessRecord pr = app; pr != null; pr = queue.poll()) {
if (pr != app) {
processes.add(pr);
}
- if (pr.uidRecord != null) {
- uids.put(pr.uidRecord.uid, pr.uidRecord);
+ final UidRecord uidRec = pr.getUidRecord();
+ if (uidRec != null) {
+ uids.put(uidRec.getUid(), uidRec);
}
- for (int i = pr.connections.size() - 1; i >= 0; i--) {
- ConnectionRecord cr = pr.connections.valueAt(i);
+ final ProcessServiceRecord psr = pr.mServices;
+ for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
+ ConnectionRecord cr = psr.getConnectionAt(i);
ProcessRecord service = (cr.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
? cr.binding.service.isolatedProc : cr.binding.service.app;
if (service == null || service == pr) {
continue;
}
- containsCycle |= service.mReachable;
- if (service.mReachable) {
+ containsCycle |= service.mState.isReachable();
+ if (service.mState.isReachable()) {
continue;
}
if ((cr.flags & (Context.BIND_WAIVE_PRIORITY
@@ -595,24 +621,26 @@
continue;
}
queue.offer(service);
- service.mReachable = true;
+ service.mState.setReachable(true);
// During scanning the reachable dependants, remove them from the pending oomadj
// targets list if it's possible, as they've been added into the immediate
// oomadj targets list 'processes' above.
mPendingProcessSet.remove(service);
}
- for (int i = pr.conProviders.size() - 1; i >= 0; i--) {
- ContentProviderConnection cpc = pr.conProviders.get(i);
+ final ProcessProviderRecord ppr = pr.mProviders;
+ for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) {
+ ContentProviderConnection cpc = ppr.getProviderConnectionAt(i);
ProcessRecord provider = cpc.provider.proc;
- if (provider == null || provider == pr || (containsCycle |= provider.mReachable)) {
+ if (provider == null || provider == pr
+ || (containsCycle |= provider.mState.isReachable())) {
continue;
}
- containsCycle |= provider.mReachable;
- if (provider.mReachable) {
+ containsCycle |= provider.mState.isReachable();
+ if (provider.mState.isReachable()) {
continue;
}
queue.offer(provider);
- provider.mReachable = true;
+ provider.mState.setReachable(true);
// During scanning the reachable dependants, remove them from the pending oomadj
// targets list if it's possible, as they've been added into the immediate
// oomadj targets list 'processes' above.
@@ -621,10 +649,10 @@
}
// Reset the flag
- app.mReachable = false;
+ state.setReachable(false);
int size = processes.size();
if (size > 0) {
- // Reverse the process list, since the updateOomAdjLockedInner scans from the end of it.
+ // Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it.
for (int l = 0, r = size - 1; l < r; l++, r--) {
ProcessRecord t = processes.get(l);
processes.set(l, processes.get(r));
@@ -632,13 +660,13 @@
}
mAdjSeq--;
// Update these reachable processes
- updateOomAdjLockedInner(oomAdjReason, topApp, processes, uids, containsCycle, false);
- } else if (app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ) {
+ updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, containsCycle, false);
+ } else if (state.getCurRawAdj() == ProcessList.UNKNOWN_ADJ) {
// In case the app goes from non-cached to cached but it doesn't have other reachable
// processes, its adj could be still unknown as of now, assign one.
processes.add(app);
assignCachedAdjIfNecessary(processes);
- applyOomAdjLocked(app, false, SystemClock.uptimeMillis(),
+ applyOomAdjLSP(app, false, SystemClock.uptimeMillis(),
SystemClock.elapsedRealtime());
}
mTmpProcessList.clear();
@@ -683,17 +711,20 @@
final ArrayList<ProcessRecord> processes = mTmpProcessList;
final ActiveUids uids = mTmpUidRecords;
- uids.clear();
- processes.clear();
- for (int i = mPendingProcessSet.size() - 1; i >= 0; i--) {
- final ProcessRecord app = mPendingProcessSet.valueAt(i);
- if (app.uidRecord != null) {
- uids.put(app.uidRecord.uid, app.uidRecord);
+ synchronized (mProcLock) {
+ uids.clear();
+ processes.clear();
+ for (int i = mPendingProcessSet.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = mPendingProcessSet.valueAt(i);
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null) {
+ uids.put(uidRec.getUid(), uidRec);
+ }
+ processes.add(app);
}
- processes.add(app);
- }
- updateOomAdjLockedInner(oomAdjReason, topApp, processes, uids, true, false);
+ updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, true, false);
+ }
processes.clear();
mPendingProcessSet.clear();
@@ -706,8 +737,8 @@
* list if the given list is null; when it's partial update, each process's client proc won't
* get evaluated recursively here.
*/
- @GuardedBy("mService")
- private void updateOomAdjLockedInner(String oomAdjReason, final ProcessRecord topApp,
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateOomAdjInnerLSP(String oomAdjReason, final ProcessRecord topApp,
ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
boolean startProfiling) {
if (startProfiling) {
@@ -719,7 +750,7 @@
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
final boolean fullUpdate = processes == null;
ActiveUids activeUids = uids;
- ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.mLruProcesses
+ ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP()
: processes;
final int numProc = activeProcesses.size();
@@ -728,8 +759,8 @@
activeUids = mTmpUidRecords;
activeUids.clear();
for (int i = 0; i < numUids; i++) {
- UidRecord r = mActiveUids.valueAt(i);
- activeUids.put(r.uid, r);
+ UidRecord uidRec = mActiveUids.valueAt(i);
+ activeUids.put(uidRec.getUid(), uidRec);
}
}
@@ -751,36 +782,39 @@
boolean retryCycles = false;
boolean computeClients = fullUpdate || potentialCycles;
- // need to reset cycle state before calling computeOomAdjLocked because of service conns
+ // need to reset cycle state before calling computeOomAdjLSP because of service conns
for (int i = numProc - 1; i >= 0; i--) {
ProcessRecord app = activeProcesses.get(i);
- app.mReachable = false;
+ final ProcessStateRecord state = app.mState;
+ state.setReachable(false);
// No need to compute again it has been evaluated in previous iteration
- if (app.adjSeq != mAdjSeq) {
- app.containsCycle = false;
- app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
- app.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
- app.setCapability = PROCESS_CAPABILITY_NONE;
- app.resetCachedInfo();
+ if (state.getAdjSeq() != mAdjSeq) {
+ state.setContainsCycle(false);
+ state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
+ state.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
+ state.setSetCapability(PROCESS_CAPABILITY_NONE);
+ state.resetCachedInfo();
}
}
for (int i = numProc - 1; i >= 0; i--) {
ProcessRecord app = activeProcesses.get(i);
- if (!app.killedByAm && app.thread != null) {
- app.procStateChanged = false;
- computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, topApp, fullUpdate, now, false,
+ final ProcessStateRecord state = app.mState;
+ if (!app.isKilledByAm() && app.getThread() != null) {
+ state.setProcStateChanged(false);
+ computeOomAdjLSP(app, ProcessList.UNKNOWN_ADJ, topApp, fullUpdate, now, false,
computeClients); // It won't enter cycle if not computing clients.
// if any app encountered a cycle, we need to perform an additional loop later
- retryCycles |= app.containsCycle;
+ retryCycles |= state.containsCycle();
// Keep the completedAdjSeq to up to date.
- app.completedAdjSeq = mAdjSeq;
+ state.setCompletedAdjSeq(mAdjSeq);
}
}
if (mCacheOomRanker.useOomReranking()) {
- mCacheOomRanker.reRankLruCachedApps(mProcessList);
+ mCacheOomRanker.reRankLruCachedAppsLSP(mProcessList.getLruProcessesLSP(),
+ mProcessList.getLruProcessServiceStartLOSP());
}
- assignCachedAdjIfNecessary(mProcessList.mLruProcesses);
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
if (computeClients) { // There won't be cycles if we didn't compute clients above.
// Cycle strategy:
@@ -794,16 +828,18 @@
for (int i = 0; i < numProc; i++) {
ProcessRecord app = activeProcesses.get(i);
- if (!app.killedByAm && app.thread != null && app.containsCycle) {
- app.adjSeq--;
- app.completedAdjSeq--;
+ final ProcessStateRecord state = app.mState;
+ if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) {
+ state.decAdjSeq();
+ state.decCompletedAdjSeq();
}
}
for (int i = 0; i < numProc; i++) {
ProcessRecord app = activeProcesses.get(i);
- if (!app.killedByAm && app.thread != null && app.containsCycle) {
- if (computeOomAdjLocked(app, app.getCurRawAdj(), topApp, true, now,
+ final ProcessStateRecord state = app.mState;
+ if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) {
+ if (computeOomAdjLSP(app, state.getCurRawAdj(), topApp, true, now,
true, true)) {
retryCycles = true;
}
@@ -815,7 +851,7 @@
mNumNonCachedProcs = 0;
mNumCachedHiddenProcs = 0;
- boolean allChanged = updateAndTrimProcessLocked(now, nowElapsed, oldTime, activeUids);
+ boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids);
mNumServiceProcs = mNewNumServiceProcs;
if (mService.mAlwaysFinishActivities) {
@@ -825,11 +861,11 @@
}
if (allChanged) {
- mService.mAppProfiler.requestPssAllProcsLocked(now, false,
+ mService.mAppProfiler.requestPssAllProcsLPr(now, false,
mService.mProcessStats.isMemFactorLowered());
}
- updateUidsLocked(activeUids, nowElapsed);
+ updateUidsLSP(activeUids, nowElapsed);
synchronized (mService.mProcessStats.mLock) {
if (mService.mProcessStats.shouldWriteNowLocked(now)) {
@@ -856,6 +892,7 @@
}
}
+ @GuardedBy({"mService", "mProcLock"})
private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
final int numLru = lruList.size();
@@ -899,23 +936,27 @@
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
+ final ProcessStateRecord state = app.mState;
// If we haven't yet assigned the final cached adj
// to the process, do that now.
- if (!app.killedByAm && app.thread != null && app.curAdj
+ if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
>= ProcessList.UNKNOWN_ADJ) {
- switch (app.getCurProcState()) {
+ final ProcessServiceRecord psr = app.mServices;
+ switch (state.getCurProcState()) {
case PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
case ActivityManager.PROCESS_STATE_CACHED_RECENT:
// Figure out the next cached level, taking into account groups.
boolean inGroup = false;
- if (app.connectionGroup != 0) {
+ final int connectionGroup = psr.getConnectionGroup();
+ if (connectionGroup != 0) {
+ final int connectionImportance = psr.getConnectionImportance();
if (lastCachedGroupUid == app.uid
- && lastCachedGroup == app.connectionGroup) {
+ && lastCachedGroup == connectionGroup) {
// This is in the same group as the last process, just tweak
// adjustment by importance.
- if (app.connectionImportance > lastCachedGroupImportance) {
- lastCachedGroupImportance = app.connectionImportance;
+ if (connectionImportance > lastCachedGroupImportance) {
+ lastCachedGroupImportance = connectionImportance;
if (curCachedAdj < nextCachedAdj
&& curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
curCachedImpAdj++;
@@ -924,8 +965,8 @@
inGroup = true;
} else {
lastCachedGroupUid = app.uid;
- lastCachedGroup = app.connectionGroup;
- lastCachedGroupImportance = app.connectionImportance;
+ lastCachedGroup = connectionGroup;
+ lastCachedGroupImportance = connectionImportance;
}
}
if (!inGroup && curCachedAdj != nextCachedAdj) {
@@ -943,11 +984,12 @@
// This process is a cached process holding activities...
// assign it the next cached value for that type, and then
// step that cached level.
- app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
- app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
+ state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+ state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
if (DEBUG_LRU) {
Slog.d(TAG_LRU, "Assigning activity LRU #" + i
- + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+ + " adj: " + state.getCurAdj()
+ + " (curCachedAdj=" + curCachedAdj
+ " curCachedImpAdj=" + curCachedImpAdj + ")");
}
break;
@@ -969,11 +1011,11 @@
// long-running services that have dropped down to the
// cached level will be treated as empty (since their process
// state is still as a service), which is what we want.
- app.setCurRawAdj(curEmptyAdj);
- app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
+ state.setCurRawAdj(curEmptyAdj);
+ state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
if (DEBUG_LRU) {
Slog.d(TAG_LRU, "Assigning empty LRU #" + i
- + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+ + " adj: " + state.getCurAdj() + " (curEmptyAdj=" + curEmptyAdj
+ ")");
}
break;
@@ -982,9 +1024,10 @@
}
}
- private boolean updateAndTrimProcessLocked(final long now, final long nowElapsed,
+ @GuardedBy({"mService", "mProcLock"})
+ private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
final long oldTime, final ActiveUids activeUids) {
- ArrayList<ProcessRecord> lruList = mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
final int numLru = lruList.size();
final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
@@ -999,34 +1042,37 @@
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
- if (!app.killedByAm && app.thread != null) {
+ final ProcessStateRecord state = app.mState;
+ if (!app.isKilledByAm() && app.getThread() != null) {
// We don't need to apply the update for the process which didn't get computed
- if (app.completedAdjSeq == mAdjSeq) {
- applyOomAdjLocked(app, true, now, nowElapsed);
+ if (state.getCompletedAdjSeq() == mAdjSeq) {
+ applyOomAdjLSP(app, true, now, nowElapsed);
}
+ final ProcessServiceRecord psr = app.mServices;
// Count the number of process types.
- switch (app.getCurProcState()) {
+ switch (state.getCurProcState()) {
case PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
mNumCachedHiddenProcs++;
numCached++;
- if (app.connectionGroup != 0) {
+ final int connectionGroup = psr.getConnectionGroup();
+ if (connectionGroup != 0) {
if (lastCachedGroupUid == app.info.uid
- && lastCachedGroup == app.connectionGroup) {
+ && lastCachedGroup == connectionGroup) {
// If this process is the next in the same group, we don't
// want it to count against our limit of the number of cached
// processes, so bump up the group count to account for it.
numCachedExtraGroup++;
} else {
lastCachedGroupUid = app.info.uid;
- lastCachedGroup = app.connectionGroup;
+ lastCachedGroup = connectionGroup;
}
} else {
lastCachedGroupUid = lastCachedGroup = 0;
}
if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
- app.kill("cached #" + numCached,
+ app.killLocked("cached #" + numCached,
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
true);
@@ -1034,17 +1080,16 @@
break;
case PROCESS_STATE_CACHED_EMPTY:
if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
- && app.lastActivityTime < oldTime) {
- app.kill("empty for "
- + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
- / 1000) + "s",
+ && app.getLastActivityTime() < oldTime) {
+ app.killLocked("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME
+ - app.getLastActivityTime()) / 1000) + "s",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
true);
} else {
numEmpty++;
if (numEmpty > emptyProcessLimit) {
- app.kill("empty #" + numEmpty,
+ app.killLocked("empty #" + numEmpty,
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
true);
@@ -1056,8 +1101,8 @@
break;
}
- if (app.isolated && app.numberOfRunningServices() <= 0
- && app.isolatedEntryPoint == null) {
+ if (app.isolated && psr.numberOfRunningServices() <= 0
+ && app.getIsolatedEntryPoint() == null) {
// If this is an isolated process, there are no services
// running in it, and it's not a special process with a
// custom entry point, then the process is no longer
@@ -1065,40 +1110,56 @@
// definition not re-use the same process again, and it is
// good to avoid having whatever code was running in them
// left sitting around after no longer needed.
- app.kill("isolated not needed", ApplicationExitInfo.REASON_OTHER,
+ app.killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
} else {
// Keeping this process, update its uid.
- updateAppUidRecLocked(app);
+ updateAppUidRecLSP(app);
}
- if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
- && !app.killedByAm) {
+ if (state.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
+ && !app.isKilledByAm()) {
numTrimming++;
}
}
}
- mProcessList.incrementProcStateSeqAndNotifyAppsLocked(activeUids);
+ mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(activeUids);
- return mService.mAppProfiler.updateLowMemStateLocked(numCached, numEmpty, numTrimming);
+ return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);
}
- private void updateAppUidRecLocked(ProcessRecord app) {
- final UidRecord uidRec = app.uidRecord;
- if (uidRec != null) {
- uidRec.ephemeral = app.info.isInstantApp();
- if (uidRec.getCurProcState() > app.getCurProcState()) {
- uidRec.setCurProcState(app.getCurProcState());
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
+ if (!app.isKilledByAm() && app.getThread() != null) {
+ if (app.isolated && app.mServices.numberOfRunningServices() <= 0
+ && app.getIsolatedEntryPoint() == null) {
+ // No op.
+ } else {
+ // Keeping this process, update its uid.
+ updateAppUidRecLSP(app);
}
- if (app.hasForegroundServices()) {
- uidRec.foregroundServices = true;
- }
- uidRec.curCapability |= app.curCapability;
}
}
- private void updateUidsLocked(ActiveUids activeUids, final long nowElapsed) {
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateAppUidRecLSP(ProcessRecord app) {
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null) {
+ final ProcessStateRecord state = app.mState;
+ uidRec.setEphemeral(app.info.isInstantApp());
+ if (uidRec.getCurProcState() > state.getCurProcState()) {
+ uidRec.setCurProcState(state.getCurProcState());
+ }
+ if (app.mServices.hasForegroundServices()) {
+ uidRec.setForegroundServices(true);
+ }
+ uidRec.setCurCapability(uidRec.getCurCapability() | state.getCurCapability());
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
ArrayList<UidRecord> becameIdle = mTmpBecameIdle;
becameIdle.clear();
@@ -1110,22 +1171,22 @@
final UidRecord uidRec = activeUids.valueAt(i);
int uidChange = UidRecord.CHANGE_PROCSTATE;
if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
- && (uidRec.setProcState != uidRec.getCurProcState()
- || uidRec.setCapability != uidRec.curCapability
- || uidRec.mSetAllowlist != uidRec.mCurAllowlist)) {
+ && (uidRec.getSetProcState() != uidRec.getCurProcState()
+ || uidRec.getSetCapability() != uidRec.getCurCapability()
+ || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
- + ": proc state from " + uidRec.setProcState + " to "
+ + ": proc state from " + uidRec.getSetProcState() + " to "
+ uidRec.getCurProcState() + ", capability from "
- + uidRec.setCapability + " to " + uidRec.curCapability
- + ", allowlist from " + uidRec.mSetAllowlist
- + " to " + uidRec.mCurAllowlist);
+ + uidRec.getSetCapability() + " to " + uidRec.getCurCapability()
+ + ", allowlist from " + uidRec.isSetAllowListed()
+ + " to " + uidRec.isCurAllowListed());
if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
- && !uidRec.mCurAllowlist) {
+ && !uidRec.isCurAllowListed()) {
// UID is now in the background (and not on the temp allowlist). Was it
// previously in the foreground (or on the temp allowlist)?
- if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
- || uidRec.mSetAllowlist) {
- uidRec.lastBackgroundTime = nowElapsed;
+ if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
+ || uidRec.isSetAllowListed()) {
+ uidRec.setLastBackgroundTime(nowElapsed);
if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
// Note: the background settle time is in elapsed realtime, while
// the handler time base is uptime. All this means is that we may
@@ -1135,38 +1196,40 @@
mConstants.BACKGROUND_SETTLE_TIME);
}
}
- if (uidRec.idle && !uidRec.setIdle) {
+ if (uidRec.isIdle() && !uidRec.isSetIdle()) {
uidChange = UidRecord.CHANGE_IDLE;
becameIdle.add(uidRec);
}
} else {
- if (uidRec.idle) {
+ if (uidRec.isIdle()) {
uidChange = UidRecord.CHANGE_ACTIVE;
- EventLogTags.writeAmUidActive(uidRec.uid);
- uidRec.idle = false;
+ EventLogTags.writeAmUidActive(uidRec.getUid());
+ uidRec.setIdle(false);
}
- uidRec.lastBackgroundTime = 0;
+ uidRec.setLastBackgroundTime(0);
}
- final boolean wasCached = uidRec.setProcState
+ final boolean wasCached = uidRec.getSetProcState()
> ActivityManager.PROCESS_STATE_RECEIVER;
final boolean isCached = uidRec.getCurProcState()
> ActivityManager.PROCESS_STATE_RECEIVER;
- if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) {
+ if (wasCached != isCached
+ || uidRec.getSetProcState() == PROCESS_STATE_NONEXISTENT) {
uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
}
- uidRec.setProcState = uidRec.getCurProcState();
- uidRec.setCapability = uidRec.curCapability;
- uidRec.mSetAllowlist = uidRec.mCurAllowlist;
- uidRec.setIdle = uidRec.idle;
- mService.mAtmInternal.onUidProcStateChanged(uidRec.uid, uidRec.setProcState);
+ uidRec.setSetProcState(uidRec.getCurProcState());
+ uidRec.setSetCapability(uidRec.getCurCapability());
+ uidRec.setSetAllowListed(uidRec.isCurAllowListed());
+ uidRec.setSetIdle(uidRec.isIdle());
+ mService.mAtmInternal.onUidProcStateChanged(
+ uidRec.getUid(), uidRec.getSetProcState());
mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
- mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
- uidRec.curCapability);
- if (uidRec.foregroundServices) {
+ mService.noteUidProcessState(uidRec.getUid(), uidRec.getCurProcState(),
+ uidRec.getCurCapability());
+ if (uidRec.hasForegroundServices()) {
mService.mServices.foregroundServiceProcStateChangedLocked(uidRec);
}
}
- mService.mInternal.deletePendingTopUid(uidRec.uid);
+ mService.mInternal.deletePendingTopUid(uidRec.getUid());
}
if (mLocalPowerManager != null) {
mLocalPowerManager.finishUidChanges();
@@ -1177,7 +1240,7 @@
// If we have any new uids that became idle this time, we need to make sure
// they aren't left with running services.
for (int i = size - 1; i >= 0; i--) {
- mService.mServices.stopInBackgroundLocked(becameIdle.get(i).uid);
+ mService.mServices.stopInBackgroundLocked(becameIdle.get(i).getUid());
}
}
}
@@ -1185,7 +1248,7 @@
private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
new ComputeOomAdjWindowCallback();
- /** These methods are called inline during computeOomAdjLocked(), on the same thread */
+ /** These methods are called inline during computeOomAdjLSP(), on the same thread */
final class ComputeOomAdjWindowCallback
implements WindowProcessController.ComputeOomAdjCallback {
@@ -1197,6 +1260,7 @@
int appUid;
int logUid;
int processStateCurTop;
+ ProcessStateRecord mState;
void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
@@ -1208,6 +1272,7 @@
this.appUid = appUid;
this.logUid = logUid;
this.processStateCurTop = processStateCurTop;
+ this.mState = app.mState;
}
@Override
@@ -1215,14 +1280,14 @@
// App has a visible activity; only upgrade adjustment.
if (adj > ProcessList.VISIBLE_APP_ADJ) {
adj = ProcessList.VISIBLE_APP_ADJ;
- app.adjType = "vis-activity";
+ mState.setAdjType("vis-activity");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
}
}
if (procState > processStateCurTop) {
procState = processStateCurTop;
- app.adjType = "vis-activity";
+ mState.setAdjType("vis-activity");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to vis-activity (top): " + app);
@@ -1231,8 +1296,8 @@
if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
- app.setCached(false);
- app.empty = false;
+ mState.setCached(false);
+ mState.setEmpty(false);
foregroundActivities = true;
}
@@ -1240,14 +1305,14 @@
public void onPausedActivity() {
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- app.adjType = "pause-activity";
+ mState.setAdjType("pause-activity");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app);
}
}
if (procState > processStateCurTop) {
procState = processStateCurTop;
- app.adjType = "pause-activity";
+ mState.setAdjType("pause-activity");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to pause-activity (top): " + app);
@@ -1256,8 +1321,8 @@
if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
- app.setCached(false);
- app.empty = false;
+ mState.setCached(false);
+ mState.setEmpty(false);
foregroundActivities = true;
}
@@ -1265,7 +1330,7 @@
public void onStoppingActivity(boolean finishing) {
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- app.adjType = "stop-activity";
+ mState.setAdjType("stop-activity");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to stop-activity: " + app);
@@ -1281,15 +1346,15 @@
if (!finishing) {
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
procState = PROCESS_STATE_LAST_ACTIVITY;
- app.adjType = "stop-activity";
+ mState.setAdjType("stop-activity");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to stop-activity: " + app);
}
}
}
- app.setCached(false);
- app.empty = false;
+ mState.setCached(false);
+ mState.setEmpty(false);
foregroundActivities = true;
}
@@ -1297,7 +1362,7 @@
public void onOtherActivity() {
if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
procState = PROCESS_STATE_CACHED_ACTIVITY;
- app.adjType = "cch-act";
+ mState.setAdjType("cch-act");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to cached activity: " + app);
@@ -1306,99 +1371,102 @@
}
}
- private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj,
+ @GuardedBy({"mService", "mProcLock"})
+ private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
boolean computeClients) {
- if (mAdjSeq == app.adjSeq) {
- if (app.adjSeq == app.completedAdjSeq) {
+ final ProcessStateRecord state = app.mState;
+ if (mAdjSeq == state.getAdjSeq()) {
+ if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
// This adjustment has already been computed successfully.
return false;
} else {
// The process is being computed, so there is a cycle. We cannot
// rely on this process's state.
- app.containsCycle = true;
+ state.setContainsCycle(true);
return false;
}
}
- if (app.thread == null) {
- app.adjSeq = mAdjSeq;
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
- app.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
- app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
- app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
- app.completedAdjSeq = app.adjSeq;
- app.curCapability = PROCESS_CAPABILITY_NONE;
+ if (app.getThread() == null) {
+ state.setAdjSeq(mAdjSeq);
+ state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
+ state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
+ state.setCurAdj(ProcessList.CACHED_APP_MAX_ADJ);
+ state.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
+ state.setCompletedAdjSeq(state.getAdjSeq());
+ state.setCurCapability(PROCESS_CAPABILITY_NONE);
return false;
}
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
- app.adjSource = null;
- app.adjTarget = null;
- app.empty = false;
- app.setCached(false);
- app.shouldNotFreeze = false;
-
- app.resetAllowStartFgs();
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN);
+ state.setAdjSource(null);
+ state.setAdjTarget(null);
+ state.setEmpty(false);
+ state.setCached(false);
+ state.setAllowStartFgsState(PROCESS_STATE_NONEXISTENT);
+ state.resetAllowStartFgs();
+ app.mOptRecord.setShouldNotFreeze(false);
final int appUid = app.info.uid;
final int logUid = mService.mCurOomAdjUid;
- int prevAppAdj = app.curAdj;
- int prevProcState = app.getCurProcState();
- int prevCapability = app.curCapability;
+ int prevAppAdj = state.getCurAdj();
+ int prevProcState = state.getCurProcState();
+ int prevCapability = state.getCurCapability();
+ final ProcessServiceRecord psr = app.mServices;
- if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+ if (state.getMaxAdj() <= ProcessList.FOREGROUND_APP_ADJ) {
// The max adjustment doesn't allow this app to be anything
// below foreground, so it is not worth doing work for it.
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
}
- app.adjType = "fixed";
- app.adjSeq = mAdjSeq;
- app.setCurRawAdj(app.maxAdj);
- app.setHasForegroundActivities(false);
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
- app.curCapability = PROCESS_CAPABILITY_ALL;
- app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
+ state.setAdjType("fixed");
+ state.setAdjSeq(mAdjSeq);
+ state.setCurRawAdj(state.getMaxAdj());
+ state.setHasForegroundActivities(false);
+ state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+ state.setCurCapability(PROCESS_CAPABILITY_ALL);
+ state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
// System processes can do UI, and when they do we want to have
// them trim their memory after the user leaves the UI. To
// facilitate this, here we need to determine whether or not it
// is currently showing UI.
- app.systemNoUi = true;
+ state.setSystemNoUi(true);
if (app == topApp) {
- app.systemNoUi = false;
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
- app.adjType = "pers-top-activity";
- } else if (app.hasTopUi()) {
+ state.setSystemNoUi(false);
+ state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+ state.setAdjType("pers-top-activity");
+ } else if (state.hasTopUi()) {
// sched group/proc state adjustment is below
- app.systemNoUi = false;
- app.adjType = "pers-top-ui";
- } else if (app.getCachedHasVisibleActivities()) {
- app.systemNoUi = false;
+ state.setSystemNoUi(false);
+ state.setAdjType("pers-top-ui");
+ } else if (state.getCachedHasVisibleActivities()) {
+ state.setSystemNoUi(false);
}
- if (!app.systemNoUi) {
+ if (!state.isSystemNoUi()) {
if (mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE) {
// screen on, promote UI
- app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+ state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+ state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
} else {
// screen off, restrict UI scheduling
- app.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
+ state.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
}
}
- app.setCurRawProcState(app.getCurProcState());
- app.curAdj = app.maxAdj;
- app.completedAdjSeq = app.adjSeq;
- app.bumpAllowStartFgsState(app.getCurProcState());
- app.setAllowStartFgs();
+ state.setCurRawProcState(state.getCurProcState());
+ state.setCurAdj(state.getMaxAdj());
+ state.setCompletedAdjSeq(state.getAdjSeq());
+ state.bumpAllowStartFgsState(state.getCurProcState());
+ state.setAllowStartFgs();
// if curAdj is less than prevAppAdj, then this process was promoted
- return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
+ return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
}
- app.systemNoUi = false;
+ state.setSystemNoUi(false);
final int PROCESS_STATE_CUR_TOP = mService.mAtmInternal.getTopProcessState();
@@ -1415,17 +1483,17 @@
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
- app.adjType = "top-activity";
+ state.setAdjType("top-activity");
foregroundActivities = true;
procState = PROCESS_STATE_CUR_TOP;
- app.bumpAllowStartFgsState(PROCESS_STATE_TOP);
+ state.bumpAllowStartFgsState(PROCESS_STATE_TOP);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
}
- } else if (app.runningRemoteAnimation) {
+ } else if (state.isRunningRemoteAnimation()) {
adj = ProcessList.VISIBLE_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
- app.adjType = "running-remote-anim";
+ state.setAdjType("running-remote-anim");
procState = PROCESS_STATE_CUR_TOP;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app);
@@ -1434,12 +1502,12 @@
// Don't want to kill running instrumentation.
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- app.adjType = "instrumentation";
+ state.setAdjType("instrumentation");
procState = PROCESS_STATE_FOREGROUND_SERVICE;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
}
- } else if (app.getCachedIsReceivingBroadcast(mTmpBroadcastQueue)) {
+ } else if (state.getCachedIsReceivingBroadcast(mTmpBroadcastQueue)) {
// An app that is currently receiving a broadcast also
// counts as being in the foreground for OOM killer purposes.
// It's placed in a sched group based on the nature of the
@@ -1447,27 +1515,26 @@
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = (mTmpBroadcastQueue.contains(mService.mFgBroadcastQueue))
? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
- app.adjType = "broadcast";
+ state.setAdjType("broadcast");
procState = ActivityManager.PROCESS_STATE_RECEIVER;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app);
}
- } else if (app.executingServices.size() > 0) {
+ } else if (psr.numberOfExecutingServices() > 0) {
// An app that is currently executing a service callback also
// counts as being in the foreground.
adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = app.execServicesFg ?
- ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
- app.adjType = "exec-service";
+ schedGroup = psr.shouldExecServicesFg()
+ ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+ state.setAdjType("exec-service");
procState = PROCESS_STATE_SERVICE;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
}
- //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
} else if (app == topApp) {
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.adjType = "top-sleeping";
+ state.setAdjType("top-sleeping");
foregroundActivities = true;
procState = PROCESS_STATE_CUR_TOP;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1480,10 +1547,10 @@
// value that the caller wants us to.
adj = cachedAdj;
procState = PROCESS_STATE_CACHED_EMPTY;
- if (!app.containsCycle) {
- app.setCached(true);
- app.empty = true;
- app.adjType = "cch-empty";
+ if (!state.containsCycle()) {
+ state.setCached(true);
+ state.setEmpty(true);
+ state.setAdjType("cch-empty");
}
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app);
@@ -1491,20 +1558,20 @@
}
// Examine all activities if not already foreground.
- if (!foregroundActivities && app.getCachedHasActivities()) {
- app.computeOomAdjFromActivitiesIfNecessary(mTmpComputeOomAdjWindowCallback,
+ if (!foregroundActivities && state.getCachedHasActivities()) {
+ state.computeOomAdjFromActivitiesIfNecessary(mTmpComputeOomAdjWindowCallback,
adj, foregroundActivities, procState, schedGroup, appUid, logUid,
PROCESS_STATE_CUR_TOP);
- adj = app.mCachedAdj;
- foregroundActivities = app.mCachedForegroundActivities;
- procState = app.mCachedProcState;
- schedGroup = app.mCachedSchedGroup;
+ adj = state.getCachedAdj();
+ foregroundActivities = state.getCachedForegroundActivities();
+ procState = state.getCachedProcState();
+ schedGroup = state.getCachedSchedGroup();
}
- if (procState > PROCESS_STATE_CACHED_RECENT && app.getCachedHasRecentTasks()) {
+ if (procState > PROCESS_STATE_CACHED_RECENT && state.getCachedHasRecentTasks()) {
procState = PROCESS_STATE_CACHED_RECENT;
- app.adjType = "cch-rec";
+ state.setAdjType("cch-rec");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app);
}
@@ -1512,24 +1579,24 @@
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|| procState > PROCESS_STATE_FOREGROUND_SERVICE) {
- if (app.hasForegroundServices()) {
+ if (psr.hasForegroundServices()) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
procState = PROCESS_STATE_FOREGROUND_SERVICE;
- app.bumpAllowStartFgsState(PROCESS_STATE_FOREGROUND_SERVICE);
- app.adjType = "fg-service";
- app.setCached(false);
+ state.bumpAllowStartFgsState(PROCESS_STATE_FOREGROUND_SERVICE);
+ state.setAdjType("fg-service");
+ state.setCached(false);
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + app.adjType + ": "
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + state.getAdjType() + ": "
+ app + " ");
}
- } else if (app.hasOverlayUi()) {
+ } else if (state.hasOverlayUi()) {
// The process is display an overlay UI.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- app.setCached(false);
- app.adjType = "has-overlay-ui";
+ state.setCached(false);
+ state.setAdjType("has-overlay-ui");
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
@@ -1540,11 +1607,11 @@
// If the app was recently in the foreground and moved to a foreground service status,
// allow it to get a higher rank in memory for some time, compared to other foreground
// services so that it can finish performing any persistence/processing of in-memory state.
- if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
- && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
- || app.setProcState <= PROCESS_STATE_TOP)) {
+ if (psr.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
+ && (state.getLastTopTime() + mConstants.TOP_TO_FGS_GRACE_DURATION > now
+ || state.getSetProcState() <= PROCESS_STATE_TOP)) {
adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
- app.adjType = "fg-service-act";
+ state.setAdjType("fg-service-act");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
}
@@ -1552,15 +1619,15 @@
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|| procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
- if (app.forcingToImportant != null) {
+ if (state.getForcingToImportant() != null) {
// This is currently used for toasts... they are not interactive, and
// we don't want them to cause the app to become fully foreground (and
// thus out of background check), so we yes the best background level we can.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
- app.setCached(false);
- app.adjType = "force-imp";
- app.adjSource = app.forcingToImportant;
+ state.setCached(false);
+ state.setAdjType("force-imp");
+ state.setAdjSource(state.getForcingToImportant());
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
@@ -1568,63 +1635,63 @@
}
}
- if (app.getCachedIsHeavyWeight()) {
+ if (state.getCachedIsHeavyWeight()) {
if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
// We don't want to kill the current heavy-weight process.
adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.setCached(false);
- app.adjType = "heavy";
+ state.setCached(false);
+ state.setAdjType("heavy");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
}
}
if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
- app.adjType = "heavy";
+ state.setAdjType("heavy");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app);
}
}
}
- if (app.getCachedIsHomeProcess()) {
+ if (state.getCachedIsHomeProcess()) {
if (adj > ProcessList.HOME_APP_ADJ) {
// This process is hosting what we currently consider to be the
// home app, so we don't want to let it go into the background.
adj = ProcessList.HOME_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.setCached(false);
- app.adjType = "home";
+ state.setCached(false);
+ state.setAdjType("home");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
}
}
if (procState > ActivityManager.PROCESS_STATE_HOME) {
procState = ActivityManager.PROCESS_STATE_HOME;
- app.adjType = "home";
+ state.setAdjType("home");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
}
}
}
- if (app.getCachedIsPreviousProcess() && app.getCachedHasActivities()) {
+ if (state.getCachedIsPreviousProcess() && state.getCachedHasActivities()) {
if (adj > ProcessList.PREVIOUS_APP_ADJ) {
// This was the previous process that showed UI to the user.
// We want to try to keep it around more aggressively, to give
// a good experience around switching between two apps.
adj = ProcessList.PREVIOUS_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.setCached(false);
- app.adjType = "previous";
+ state.setCached(false);
+ state.setAdjType("previous");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
}
}
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
procState = PROCESS_STATE_LAST_ACTIVITY;
- app.adjType = "previous";
+ state.setAdjType("previous");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
}
@@ -1632,7 +1699,7 @@
}
if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
- + " reason=" + app.adjType);
+ + " reason=" + state.getAdjType());
// By default, we use the computed adjustment. It may be changed if
// there are applications dependent on our services or providers, but
@@ -1640,15 +1707,15 @@
// infinite recursion. If we're re-evaluating due to cycles, use the previously computed
// values.
if (cycleReEval) {
- procState = Math.min(procState, app.getCurRawProcState());
- adj = Math.min(adj, app.getCurRawAdj());
- schedGroup = Math.max(schedGroup, app.getCurrentSchedulingGroup());
+ procState = Math.min(procState, state.getCurRawProcState());
+ adj = Math.min(adj, state.getCurRawAdj());
+ schedGroup = Math.max(schedGroup, state.getCurrentSchedulingGroup());
}
- app.setCurRawAdj(adj);
- app.setCurRawProcState(procState);
+ state.setCurRawAdj(adj);
+ state.setCurRawProcState(procState);
- app.hasStartedServices = false;
- app.adjSeq = mAdjSeq;
+ state.setHasStartedServices(false);
+ state.setAdjSeq(mAdjSeq);
final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
if (backupTarget != null && app == backupTarget.app) {
@@ -1659,15 +1726,15 @@
if (procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
}
- app.adjType = "backup";
+ state.setAdjType("backup");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
}
- app.setCached(false);
+ state.setCached(false);
}
if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
procState = ActivityManager.PROCESS_STATE_BACKUP;
- app.adjType = "backup";
+ state.setAdjType("backup");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app);
}
@@ -1675,29 +1742,29 @@
}
int capabilityFromFGS = 0; // capability from foreground service.
- for (int is = app.numberOfRunningServices() - 1;
+ for (int is = psr.numberOfRunningServices() - 1;
is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
|| procState > PROCESS_STATE_TOP);
is--) {
- ServiceRecord s = app.getRunningServiceAt(is);
+ ServiceRecord s = psr.getRunningServiceAt(is);
if (s.startRequested) {
- app.hasStartedServices = true;
+ state.setHasStartedServices(true);
if (procState > PROCESS_STATE_SERVICE) {
procState = PROCESS_STATE_SERVICE;
- app.adjType = "started-services";
+ state.setAdjType("started-services");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to started service: " + app);
}
}
- if (!s.mKeepWarming && app.hasShownUi && !app.getCachedIsHomeProcess()) {
+ if (!s.mKeepWarming && state.hasShownUi() && !state.getCachedIsHomeProcess()) {
// If this process has shown some UI, let it immediately
// go to the LRU list because it may be pretty heavy with
// UI stuff. We'll tag it with a label just to help
// debug and understand what is going on.
if (adj > ProcessList.SERVICE_ADJ) {
- app.adjType = "cch-started-ui-services";
+ state.setAdjType("cch-started-ui-services");
}
} else {
if (s.mKeepWarming
@@ -1707,19 +1774,19 @@
// of the background processes.
if (adj > ProcessList.SERVICE_ADJ) {
adj = ProcessList.SERVICE_ADJ;
- app.adjType = "started-services";
+ state.setAdjType("started-services");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to started service: " + app);
}
- app.setCached(false);
+ state.setCached(false);
}
}
// If we have let the service slide into the background
// state, still have some text describing what it is doing
// even though the service no longer has an impact.
if (adj > ProcessList.SERVICE_ADJ) {
- app.adjType = "cch-started-services";
+ state.setAdjType("cch-started-services");
}
}
}
@@ -1774,29 +1841,31 @@
boolean trackedProcState = false;
ProcessRecord client = cr.binding.client;
+ final ProcessStateRecord cstate = client.mState;
if (computeClients) {
- computeOomAdjLocked(client, cachedAdj, topApp, doingAll, now,
+ computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now,
cycleReEval, true);
} else {
- client.setCurRawAdj(client.setAdj);
- client.setCurRawProcState(client.setProcState);
+ cstate.setCurRawAdj(cstate.getSetAdj());
+ cstate.setCurRawProcState(cstate.getSetProcState());
}
- int clientAdj = client.getCurRawAdj();
- int clientProcState = client.getCurRawProcState();
+ int clientAdj = cstate.getCurRawAdj();
+ int clientProcState = cstate.getCurRawProcState();
// pass client's mAllowStartFgs to the app if client is not persistent process.
- if (client.mAllowStartFgs && client.maxAdj >= ProcessList.FOREGROUND_APP_ADJ) {
- app.mAllowStartFgs = true;
+ if (cstate.getAllowedStartFgs() != FGS_FEATURE_DENIED
+ && cstate.getMaxAdj() >= ProcessList.FOREGROUND_APP_ADJ) {
+ state.setAllowStartFgs(cstate.getAllowedStartFgs());
}
if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
- if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+ if (shouldSkipDueToCycle(state, cstate, procState, adj, cycleReEval)) {
continue;
}
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
- capability |= client.curCapability;
+ capability |= cstate.getCurCapability();
}
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
@@ -1809,7 +1878,7 @@
if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
// Not doing bind OOM management, so treat
// this guy more like a started service.
- if (app.hasShownUi && !app.getCachedIsHomeProcess()) {
+ if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
// If this process has shown some UI, let it immediately
// go to the LRU list because it may be pretty heavy with
// UI stuff. We'll tag it with a label just to help
@@ -1817,7 +1886,7 @@
if (adj > clientAdj) {
adjType = "cch-bound-ui-services";
}
- app.setCached(false);
+ state.setCached(false);
clientAdj = adj;
clientProcState = procState;
} else {
@@ -1843,7 +1912,7 @@
// about letting this process get into the LRU
// list to be killed and restarted if needed for
// memory.
- if (app.hasShownUi && !app.getCachedIsHomeProcess()
+ if (state.hasShownUi() && !state.getCachedIsHomeProcess()
&& clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
adjType = "cch-bound-ui-services";
@@ -1880,12 +1949,12 @@
newAdj = adj;
}
}
- if (!client.isCached()) {
- app.setCached(false);
+ if (!cstate.isCached()) {
+ state.setCached(false);
}
if (adj > newAdj) {
adj = newAdj;
- app.setCurRawAdj(adj);
+ state.setCurRawAdj(adj);
adjType = "service";
}
}
@@ -1895,7 +1964,7 @@
// This will treat important bound services identically to
// the top app, which may behave differently than generic
// foreground work.
- final int curSchedGroup = client.getCurrentSchedulingGroup();
+ final int curSchedGroup = cstate.getCurrentSchedulingGroup();
if (curSchedGroup > schedGroup) {
if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
schedGroup = curSchedGroup;
@@ -1910,7 +1979,7 @@
// give them the best bound state after that.
if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- app.bumpAllowStartFgsState(
+ state.bumpAllowStartFgsState(
PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
} else if (mService.mWakefulness.get()
== PowerManagerInternal.WAKEFULNESS_AWAKE
@@ -1925,7 +1994,7 @@
// Go at most to BOUND_TOP, unless requested to elevate
// to client's state.
clientProcState = PROCESS_STATE_BOUND_TOP;
- app.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
+ state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
boolean enabled = false;
try {
enabled = mPlatformCompatCache.isChangeEnabled(
@@ -1969,7 +2038,7 @@
if (procState > clientProcState) {
procState = clientProcState;
- app.setCurRawProcState(procState);
+ state.setCurRawProcState(procState);
if (adjType == null) {
adjType = "service";
}
@@ -1979,12 +2048,12 @@
app.setPendingUiClean(true);
}
if (adjType != null) {
- app.adjType = adjType;
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo
- .REASON_SERVICE_IN_USE;
- app.adjSource = cr.binding.client;
- app.adjSourceProcState = clientProcState;
- app.adjTarget = s.instanceName;
+ state.setAdjType(adjType);
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+ .REASON_SERVICE_IN_USE);
+ state.setAdjSource(cr.binding.client);
+ state.setAdjSourceProcState(clientProcState);
+ state.setAdjTarget(s.instanceName);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ ": " + app + ", due to " + cr.binding.client
@@ -2003,18 +2072,18 @@
// bound by an unfrozen app via a WPRI binding has to remain
// unfrozen.
if (clientAdj < ProcessList.CACHED_APP_MIN_ADJ) {
- app.shouldNotFreeze = true;
+ app.mOptRecord.setShouldNotFreeze(true);
}
}
if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
- app.treatLikeActivity = true;
+ psr.setTreatLikeActivity(true);
}
final ActivityServiceConnectionsHolder a = cr.activity;
if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
&& a.isActivityVisible()) {
adj = ProcessList.FOREGROUND_APP_ADJ;
- app.setCurRawAdj(adj);
+ state.setCurRawAdj(adj);
if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -2022,13 +2091,13 @@
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
}
- app.setCached(false);
- app.adjType = "service";
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo
- .REASON_SERVICE_IN_USE;
- app.adjSource = a;
- app.adjSourceProcState = procState;
- app.adjTarget = s.instanceName;
+ state.setCached(false);
+ state.setAdjType("service");
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+ .REASON_SERVICE_IN_USE);
+ state.setAdjSource(a);
+ state.setAdjSourceProcState(procState);
+ state.setAdjTarget(s.instanceName);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise to service w/activity: " + app);
@@ -2039,12 +2108,13 @@
}
}
- for (int provi = app.pubProviders.size() - 1;
+ final ProcessProviderRecord ppr = app.mProviders;
+ for (int provi = ppr.numberOfProviders() - 1;
provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
|| procState > PROCESS_STATE_TOP);
provi--) {
- ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
+ ContentProviderRecord cpr = ppr.getProviderAt(provi);
for (int i = cpr.connections.size() - 1;
i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
@@ -2052,24 +2122,24 @@
i--) {
ContentProviderConnection conn = cpr.connections.get(i);
ProcessRecord client = conn.client;
+ final ProcessStateRecord cstate = client.mState;
if (client == app) {
// Being our own client is not interesting.
continue;
}
if (computeClients) {
- computeOomAdjLocked(client, cachedAdj, topApp, doingAll, now, cycleReEval,
- true);
+ computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true);
} else {
- client.setCurRawAdj(client.setAdj);
- client.setCurRawProcState(client.setProcState);
+ cstate.setCurRawAdj(cstate.getSetAdj());
+ cstate.setCurRawProcState(cstate.getSetProcState());
}
- if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+ if (shouldSkipDueToCycle(state, cstate, procState, adj, cycleReEval)) {
continue;
}
- int clientAdj = client.getCurRawAdj();
- int clientProcState = client.getCurRawProcState();
+ int clientAdj = cstate.getCurRawAdj();
+ int clientProcState = cstate.getCurRawProcState();
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
// If the other app is cached for any reason, for purposes here
@@ -2078,16 +2148,16 @@
}
String adjType = null;
if (adj > clientAdj) {
- if (app.hasShownUi && !app.getCachedIsHomeProcess()
+ if (state.hasShownUi() && !state.getCachedIsHomeProcess()
&& clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
adjType = "cch-ui-provider";
} else {
adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
- app.setCurRawAdj(adj);
+ state.setCurRawAdj(adj);
adjType = "provider";
}
- app.setCached(app.isCached() & client.isCached());
+ state.setCached(state.isCached() & cstate.isCached());
}
if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -2104,18 +2174,18 @@
conn.trackProcState(clientProcState, mAdjSeq, now);
if (procState > clientProcState) {
procState = clientProcState;
- app.setCurRawProcState(procState);
+ state.setCurRawProcState(procState);
}
- if (client.getCurrentSchedulingGroup() > schedGroup) {
+ if (cstate.getCurrentSchedulingGroup() > schedGroup) {
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
if (adjType != null) {
- app.adjType = adjType;
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo
- .REASON_PROVIDER_IN_USE;
- app.adjSource = client;
- app.adjSourceProcState = clientProcState;
- app.adjTarget = cpr.name;
+ state.setAdjType(adjType);
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+ .REASON_PROVIDER_IN_USE);
+ state.setAdjSource(client);
+ state.setAdjSourceProcState(clientProcState);
+ state.setAdjTarget(cpr.name);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ ": " + app + ", due to " + client
@@ -2130,11 +2200,11 @@
if (cpr.hasExternalProcessHandles()) {
if (adj > ProcessList.FOREGROUND_APP_ADJ) {
adj = ProcessList.FOREGROUND_APP_ADJ;
- app.setCurRawAdj(adj);
+ state.setCurRawAdj(adj);
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- app.setCached(false);
- app.adjType = "ext-provider";
- app.adjTarget = cpr.name;
+ state.setCached(false);
+ state.setAdjType("ext-provider");
+ state.setAdjTarget(cpr.name);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to external provider: " + app);
@@ -2142,7 +2212,7 @@
}
if (procState > PROCESS_STATE_IMPORTANT_FOREGROUND) {
procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- app.setCurRawProcState(procState);
+ state.setCurRawProcState(procState);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to external provider: " + app);
@@ -2151,13 +2221,13 @@
}
}
- if (app.lastProviderTime > 0 &&
- (app.lastProviderTime + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+ if (ppr.getLastProviderTime() > 0
+ && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
if (adj > ProcessList.PREVIOUS_APP_ADJ) {
adj = ProcessList.PREVIOUS_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.setCached(false);
- app.adjType = "recent-provider";
+ state.setCached(false);
+ state.setAdjType("recent-provider");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to recent provider: " + app);
@@ -2165,7 +2235,7 @@
}
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
procState = PROCESS_STATE_LAST_ACTIVITY;
- app.adjType = "recent-provider";
+ state.setAdjType("recent-provider");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to recent provider: " + app);
@@ -2174,24 +2244,23 @@
}
if (procState >= PROCESS_STATE_CACHED_EMPTY) {
- if (app.hasClientActivities()) {
+ if (psr.hasClientActivities()) {
// This is a cached process, but with client activities. Mark it so.
procState = PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
- app.adjType = "cch-client-act";
- } else if (app.treatLikeActivity) {
+ state.setAdjType("cch-client-act");
+ } else if (psr.isTreatedLikeActivity()) {
// This is a cached process, but somebody wants us to treat it like it has
// an activity, okay!
procState = PROCESS_STATE_CACHED_ACTIVITY;
- app.adjType = "cch-as-act";
+ state.setAdjType("cch-as-act");
}
}
if (adj == ProcessList.SERVICE_ADJ) {
if (doingAll && !cycleReEval) {
- app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
+ state.setServiceB(mNewNumAServiceProcs > (mNumServiceProcs / 3));
mNewNumServiceProcs++;
- //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
- if (!app.serviceb) {
+ if (!state.isServiceB()) {
// This service isn't far enough down on the LRU list to
// normally be a B service, but if we are low on RAM and it
// is large we want to force it down since we would prefer to
@@ -2199,29 +2268,27 @@
if (!mService.mAppProfiler.isLastMemoryLevelNormal()
&& app.mProfile.getLastPss()
>= mProcessList.getCachedRestoreThresholdKb()) {
- app.serviceHighRam = true;
- app.serviceb = true;
+ state.setServiceHighRam(true);
+ state.setServiceB(true);
//Slog.i(TAG, "ADJ " + app + " high ram!");
} else {
mNewNumAServiceProcs++;
//Slog.i(TAG, "ADJ " + app + " not high ram!");
}
} else {
- app.serviceHighRam = false;
+ state.setServiceHighRam(false);
}
}
- if (app.serviceb) {
+ if (state.isServiceB()) {
adj = ProcessList.SERVICE_B_ADJ;
}
}
- app.setCurRawAdj(adj);
+ state.setCurRawAdj(adj);
- //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
- // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
- if (adj > app.maxAdj) {
- adj = app.maxAdj;
- if (app.maxAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+ if (adj > state.getMaxAdj()) {
+ adj = state.getMaxAdj();
+ if (adj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
}
@@ -2236,31 +2303,32 @@
}
// apply capability from FGS.
- if (app.hasForegroundServices()) {
+ if (psr.hasForegroundServices()) {
capability |= capabilityFromFGS;
}
- capability |= getDefaultCapability(app, procState);
+ capability |= getDefaultCapability(psr, procState);
// Do final modification to adj. Everything we do between here and applying
// the final setAdj must be done in this function, because we will also use
// it when computing the final cached adj later. Note that we don't need to
// worry about this for max adj above, since max adj will always be used to
// keep it out of the cached vaues.
- app.curAdj = app.modifyRawOomAdj(adj);
- app.curCapability = capability;
- app.setCurrentSchedulingGroup(schedGroup);
- app.setCurProcState(procState);
- app.setCurRawProcState(procState);
- app.setHasForegroundActivities(foregroundActivities);
- app.completedAdjSeq = mAdjSeq;
- app.setAllowStartFgs();
+ state.setCurAdj(psr.modifyRawOomAdj(adj));
+ state.setCurCapability(capability);
+ state.setCurrentSchedulingGroup(schedGroup);
+ state.setCurProcState(procState);
+ state.setCurRawProcState(procState);
+ state.setHasForegroundActivities(foregroundActivities);
+ state.setCompletedAdjSeq(mAdjSeq);
+ state.setAllowStartFgs();
+
// if curAdj or curProcState improved, then this process was promoted
- return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState
- || app.curCapability != prevCapability ;
+ return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState
+ || state.getCurCapability() != prevCapability;
}
- private int getDefaultCapability(ProcessRecord app, int procState) {
+ private int getDefaultCapability(ProcessServiceRecord psr, int procState) {
switch (procState) {
case PROCESS_STATE_PERSISTENT:
case PROCESS_STATE_PERSISTENT_UI:
@@ -2269,7 +2337,7 @@
case PROCESS_STATE_BOUND_TOP:
return PROCESS_CAPABILITY_NONE;
case PROCESS_STATE_FOREGROUND_SERVICE:
- if (app.hasForegroundServices()) {
+ if (psr.hasForegroundServices()) {
// Capability from FGS are conditional depending on foreground service type in
// manifest file and the mAllowWhileInUsePermissionInFgs flag.
return PROCESS_CAPABILITY_NONE;
@@ -2297,16 +2365,16 @@
* evaluation.
* @return whether to skip using the client connection at this time
*/
- private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client,
+ private boolean shouldSkipDueToCycle(ProcessStateRecord app, ProcessStateRecord client,
int procState, int adj, boolean cycleReEval) {
- if (client.containsCycle) {
- // We've detected a cycle. We should retry computeOomAdjLocked later in
+ if (client.containsCycle()) {
+ // We've detected a cycle. We should retry computeOomAdjLSP later in
// case a later-checked connection from a client would raise its
// priority legitimately.
- app.containsCycle = true;
+ app.setContainsCycle(true);
// If the client has not been completely evaluated, check if it's worth
// using the partial values.
- if (client.completedAdjSeq < mAdjSeq) {
+ if (client.getCompletedAdjSeq() < mAdjSeq) {
if (cycleReEval) {
// If the partial values are no better, skip until the next
// attempt
@@ -2325,7 +2393,8 @@
}
/** Inform the oomadj observer of changes to oomadj. Used by tests. */
- void reportOomAdjMessageLocked(String tag, String msg) {
+ @GuardedBy("mService")
+ private void reportOomAdjMessageLocked(String tag, String msg) {
Slog.d(tag, msg);
synchronized (mService.mOomAdjObserverLock) {
if (mService.mCurOomAdjObserver != null) {
@@ -2336,13 +2405,14 @@
}
/** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
- @GuardedBy("mService")
- private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
+ @GuardedBy({"mService", "mProcLock"})
+ private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
long nowElapsed) {
boolean success = true;
+ final ProcessStateRecord state = app.mState;
- if (app.getCurRawAdj() != app.setRawAdj) {
- app.setRawAdj = app.getCurRawAdj();
+ if (state.getCurRawAdj() != state.getSetRawAdj()) {
+ state.setSetRawAdj(state.getCurRawAdj());
}
int changes = 0;
@@ -2350,22 +2420,22 @@
// don't compact during bootup
if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
// Cached and prev/home compaction
- if (app.curAdj != app.setAdj) {
+ if (state.getCurAdj() != state.getSetAdj()) {
// Perform a minor compaction when a perceptible app becomes the prev/home app
// Perform a major compaction when any app enters cached
// reminder: here, setAdj is previous state, curAdj is upcoming state
- if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
- (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
- app.curAdj == ProcessList.HOME_APP_ADJ)) {
+ if (state.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ
+ && (state.getCurAdj() == ProcessList.PREVIOUS_APP_ADJ
+ || state.getCurAdj() == ProcessList.HOME_APP_ADJ)) {
mCachedAppOptimizer.compactAppSome(app);
- } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
- || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
- && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
- && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
+ } else if ((state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ
+ || state.getSetAdj() > ProcessList.CACHED_APP_MAX_ADJ)
+ && state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ
+ && state.getCurAdj() <= ProcessList.CACHED_APP_MAX_ADJ) {
mCachedAppOptimizer.compactAppFull(app);
}
} else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
- && app.setAdj < ProcessList.FOREGROUND_APP_ADJ
+ && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
// Because these can fire independent of oom_adj/procstate changes, we need
// to throttle the actual dispatch of these requests in addition to the
// processing of the requests. As a result, there is throttling both here
@@ -2373,36 +2443,36 @@
&& mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
mCachedAppOptimizer.compactAppPersistent(app);
} else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
- && app.getCurProcState()
+ && state.getCurProcState()
== ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
&& mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
mCachedAppOptimizer.compactAppBfgs(app);
}
}
- if (app.curAdj != app.setAdj) {
- ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
+ if (state.getCurAdj() != state.getSetAdj()) {
+ ProcessList.setOomAdj(app.getPid(), app.uid, state.getCurAdj());
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
- String msg = "Set " + app.pid + " " + app.processName + " adj "
- + app.curAdj + ": " + app.adjType;
+ String msg = "Set " + app.getPid() + " " + app.processName + " adj "
+ + state.getCurAdj() + ": " + state.getAdjType();
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
- app.setAdj = app.curAdj;
- app.verifiedAdj = ProcessList.INVALID_ADJ;
+ state.setSetAdj(state.getCurAdj());
+ state.setVerifiedAdj(ProcessList.INVALID_ADJ);
}
- final int curSchedGroup = app.getCurrentSchedulingGroup();
- if (app.setSchedGroup != curSchedGroup) {
- int oldSchedGroup = app.setSchedGroup;
- app.setSchedGroup = curSchedGroup;
+ final int curSchedGroup = state.getCurrentSchedulingGroup();
+ if (state.getSetSchedGroup() != curSchedGroup) {
+ int oldSchedGroup = state.getSetSchedGroup();
+ state.setSetSchedGroup(curSchedGroup);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
String msg = "Setting sched group of " + app.processName
- + " to " + curSchedGroup + ": " + app.adjType;
+ + " to " + curSchedGroup + ": " + state.getAdjType();
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
- if (app.waitingToKill != null && app.curReceivers.isEmpty()
- && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
- app.kill(app.waitingToKill, ApplicationExitInfo.REASON_USER_REQUESTED,
+ if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
+ && state.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND) {
+ app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
ApplicationExitInfo.SUBREASON_UNKNOWN, true);
success = false;
} else {
@@ -2423,22 +2493,23 @@
break;
}
mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
- 0 /* unused */, app.pid, processGroup, app.processName));
+ 0 /* unused */, app.getPid(), processGroup, app.processName));
try {
+ final int renderThreadTid = app.getRenderThreadTid();
if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
// do nothing if we already switched to RT
if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
if (mService.mUseFifoUiScheduling) {
// Switch UI pipeline for app to SCHED_FIFO
- app.savedPriority = Process.getThreadPriority(app.pid);
- mService.scheduleAsFifoPriority(app.pid, /* suppressLogs */true);
- if (app.renderThreadTid != 0) {
- mService.scheduleAsFifoPriority(app.renderThreadTid,
+ state.setSavedPriority(Process.getThreadPriority(app.getPid()));
+ mService.scheduleAsFifoPriority(app.getPid(), true);
+ if (renderThreadTid != 0) {
+ mService.scheduleAsFifoPriority(renderThreadTid,
/* suppressLogs */true);
if (DEBUG_OOM_ADJ) {
Slog.d("UI_FIFO", "Set RenderThread (TID " +
- app.renderThreadTid + ") to FIFO");
+ renderThreadTid + ") to FIFO");
}
} else {
if (DEBUG_OOM_ADJ) {
@@ -2447,10 +2518,10 @@
}
} else {
// Boost priority for top app UI and render threads
- setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
- if (app.renderThreadTid != 0) {
+ setThreadPriority(app.getPid(), TOP_APP_PRIORITY_BOOST);
+ if (renderThreadTid != 0) {
try {
- setThreadPriority(app.renderThreadTid,
+ setThreadPriority(renderThreadTid,
TOP_APP_PRIORITY_BOOST);
} catch (IllegalArgumentException e) {
// thread died, ignore
@@ -2464,10 +2535,10 @@
if (mService.mUseFifoUiScheduling) {
try {
// Reset UI pipeline to SCHED_OTHER
- setThreadScheduler(app.pid, SCHED_OTHER, 0);
- setThreadPriority(app.pid, app.savedPriority);
- if (app.renderThreadTid != 0) {
- setThreadScheduler(app.renderThreadTid,
+ setThreadScheduler(app.getPid(), SCHED_OTHER, 0);
+ setThreadPriority(app.getPid(), state.getSavedPriority());
+ if (renderThreadTid != 0) {
+ setThreadScheduler(renderThreadTid,
SCHED_OTHER, 0);
}
} catch (IllegalArgumentException e) {
@@ -2479,139 +2550,141 @@
}
} else {
// Reset priority for top app UI and render threads
- setThreadPriority(app.pid, 0);
+ setThreadPriority(app.getPid(), 0);
}
- if (app.renderThreadTid != 0) {
- setThreadPriority(app.renderThreadTid, THREAD_PRIORITY_DISPLAY);
+ if (renderThreadTid != 0) {
+ setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
}
}
} catch (Exception e) {
if (DEBUG_ALL) {
- Slog.w(TAG, "Failed setting thread priority of " + app.pid, e);
+ Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
}
}
}
}
- if (app.repForegroundActivities != app.hasForegroundActivities()) {
- app.repForegroundActivities = app.hasForegroundActivities();
+ if (state.hasRepForegroundActivities() != state.hasForegroundActivities()) {
+ state.setRepForegroundActivities(state.hasForegroundActivities());
changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
}
- updateAppFreezeStateLocked(app);
+ updateAppFreezeStateLSP(app);
- if (app.getReportedProcState() != app.getCurProcState()) {
- app.setReportedProcState(app.getCurProcState());
- if (app.thread != null) {
+ if (state.getReportedProcState() != state.getCurProcState()) {
+ state.setReportedProcState(state.getCurProcState());
+ if (app.getThread() != null) {
try {
if (false) {
//RuntimeException h = new RuntimeException("here");
- Slog.i(TAG, "Sending new process state " + app.getReportedProcState()
+ Slog.i(TAG, "Sending new process state " + state.getReportedProcState()
+ " to " + app /*, h*/);
}
- app.thread.setProcessState(app.getReportedProcState());
+ app.getThread().setProcessState(state.getReportedProcState());
} catch (RemoteException e) {
}
}
}
boolean forceUpdatePssTime = false;
- if (app.setProcState == PROCESS_STATE_NONEXISTENT
- || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) {
- app.lastStateTime = now;
+ if (state.getSetProcState() == PROCESS_STATE_NONEXISTENT
+ || ProcessList.procStatesDifferForMem(
+ state.getCurProcState(), state.getSetProcState())) {
+ state.setLastStateTime(now);
forceUpdatePssTime = true;
if (DEBUG_PSS) {
Slog.d(TAG_PSS, "Process state change from "
- + ProcessList.makeProcStateString(app.setProcState) + " to "
- + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in "
+ + ProcessList.makeProcStateString(state.getSetProcState()) + " to "
+ + ProcessList.makeProcStateString(state.getCurProcState()) + " next pss in "
+ (app.mProfile.getNextPssTime() - now) + ": " + app);
}
}
synchronized (mService.mAppProfiler.mProfilerLock) {
- app.mProfile.updateProcState(app);
+ app.mProfile.updateProcState(app.mState);
mService.mAppProfiler.updateNextPssTimeLPf(
- app.getCurProcState(), app.mProfile, now, forceUpdatePssTime);
+ state.getCurProcState(), app.mProfile, now, forceUpdatePssTime);
}
- if (app.setProcState != app.getCurProcState()) {
+ if (state.getSetProcState() != state.getCurProcState()) {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
String msg = "Proc state change of " + app.processName
- + " to " + ProcessList.makeProcStateString(app.getCurProcState())
- + " (" + app.getCurProcState() + ")" + ": " + app.adjType;
+ + " to " + ProcessList.makeProcStateString(state.getCurProcState())
+ + " (" + state.getCurProcState() + ")" + ": " + state.getAdjType();
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
- boolean setImportant = app.setProcState < PROCESS_STATE_SERVICE;
- boolean curImportant = app.getCurProcState() < PROCESS_STATE_SERVICE;
+ boolean setImportant = state.getSetProcState() < PROCESS_STATE_SERVICE;
+ boolean curImportant = state.getCurProcState() < PROCESS_STATE_SERVICE;
if (setImportant && !curImportant) {
// This app is no longer something we consider important enough to allow to use
// arbitrary amounts of battery power. Note its current CPU time to later know to
// kill it if it is not behaving well.
- app.setWhenUnimportant(now);
+ state.setWhenUnimportant(now);
app.mProfile.mLastCpuTime.set(0);
}
// Inform UsageStats of important process state change
// Must be called before updating setProcState
- maybeUpdateUsageStatsLocked(app, nowElapsed);
+ maybeUpdateUsageStatsLSP(app, nowElapsed);
- maybeUpdateLastTopTime(app, now);
+ maybeUpdateLastTopTime(state, now);
- app.setProcState = app.getCurProcState();
- if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
- app.notCachedSinceIdle = false;
+ state.setSetProcState(state.getCurProcState());
+ if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_HOME) {
+ state.setNotCachedSinceIdle(false);
}
if (!doingAll) {
- mService.setProcessTrackerStateLocked(app,
+ mService.setProcessTrackerStateLOSP(app,
mService.mProcessStats.getMemFactorLocked(), now);
} else {
- app.procStateChanged = true;
+ state.setProcStateChanged(true);
}
- } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime())
+ } else if (state.hasReportedInteraction() && (nowElapsed - state.getInteractionEventTime())
> mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
// For apps that sit around for a long time in the interactive state, we need
// to report this at least once a day so they don't go idle.
- maybeUpdateUsageStatsLocked(app, nowElapsed);
- } else if (!app.reportedInteraction && (nowElapsed - app.getFgInteractionTime())
+ maybeUpdateUsageStatsLSP(app, nowElapsed);
+ } else if (!state.hasReportedInteraction() && (nowElapsed - state.getFgInteractionTime())
> mConstants.SERVICE_USAGE_INTERACTION_TIME) {
// For foreground services that sit around for a long time but are not interacted with.
- maybeUpdateUsageStatsLocked(app, nowElapsed);
+ maybeUpdateUsageStatsLSP(app, nowElapsed);
}
- if (app.curCapability != app.setCapability) {
+ if (state.getCurCapability() != state.getSetCapability()) {
changes |= ActivityManagerService.ProcessChangeItem.CHANGE_CAPABILITY;
- app.setCapability = app.curCapability;
+ state.setSetCapability(state.getCurCapability());
}
if (changes != 0) {
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"Changes in " + app + ": " + changes);
ActivityManagerService.ProcessChangeItem item =
- mProcessList.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
+ mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid);
item.changes |= changes;
- item.foregroundActivities = app.repForegroundActivities;
- item.capability = app.setCapability;
+ item.foregroundActivities = state.hasRepForegroundActivities();
+ item.capability = state.getSetCapability();
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"Item " + Integer.toHexString(System.identityHashCode(item))
+ " " + app.toShortString() + ": changes=" + item.changes
+ " foreground=" + item.foregroundActivities
- + " type=" + app.adjType + " source=" + app.adjSource
- + " target=" + app.adjTarget + " capability=" + item.capability);
+ + " type=" + state.getAdjType() + " source=" + state.getAdjSource()
+ + " target=" + state.getAdjTarget() + " capability=" + item.capability);
}
return success;
}
- @GuardedBy("mService")
- void setAttachingSchedGroupLocked(ProcessRecord app) {
+ @GuardedBy({"mService", "mProcLock"})
+ void setAttachingSchedGroupLSP(ProcessRecord app) {
int initialSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ final ProcessStateRecord state = app.mState;
// If the process has been marked as foreground via Zygote.START_FLAG_USE_TOP_APP_PRIORITY,
// then verify that the top priority is actually is applied.
- if (app.hasForegroundActivities()) {
+ if (state.hasForegroundActivities()) {
String fallbackReason = null;
try {
- // The priority must be the same as how does {@link #applyOomAdjLocked} set for
+ // The priority must be the same as how does {@link #applyOomAdjLSP} set for
// {@link ProcessList.SCHED_GROUP_TOP_APP}. We don't check render thread because it
// is not ready when attaching.
- if (Process.getProcessGroup(app.pid) == THREAD_GROUP_TOP_APP) {
+ if (Process.getProcessGroup(app.getPid()) == THREAD_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
- setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
+ setThreadPriority(app.getPid(), TOP_APP_PRIORITY_BOOST);
} else {
fallbackReason = "not expected top priority";
}
@@ -2627,23 +2700,27 @@
}
}
- app.setCurrentSchedulingGroup(app.setSchedGroup = initialSchedGroup);
+ state.setSetSchedGroup(initialSchedGroup);
+ state.setCurrentSchedulingGroup(initialSchedGroup);
}
// ONLY used for unit testing in OomAdjusterTests.java
@VisibleForTesting
void maybeUpdateUsageStats(ProcessRecord app, long nowElapsed) {
synchronized (mService) {
- maybeUpdateUsageStatsLocked(app, nowElapsed);
+ synchronized (mProcLock) {
+ maybeUpdateUsageStatsLSP(app, nowElapsed);
+ }
}
}
- @GuardedBy("mService")
- private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
+ @GuardedBy({"mService", "mProcLock"})
+ private void maybeUpdateUsageStatsLSP(ProcessRecord app, long nowElapsed) {
+ final ProcessStateRecord state = app.mState;
if (DEBUG_USAGE_STATS) {
Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
- + "] state changes: old = " + app.setProcState + ", new = "
- + app.getCurProcState());
+ + "] state changes: old = " + state.getSetProcState() + ", new = "
+ + state.getCurProcState());
}
if (mService.mUsageStatsService == null) {
return;
@@ -2652,27 +2729,28 @@
// To avoid some abuse patterns, we are going to be careful about what we consider
// to be an app interaction. Being the top activity doesn't count while the display
// is sleeping, nor do short foreground services.
- if (app.getCurProcState() <= PROCESS_STATE_TOP
- || app.getCurProcState() == PROCESS_STATE_BOUND_TOP) {
+ if (state.getCurProcState() <= PROCESS_STATE_TOP
+ || state.getCurProcState() == PROCESS_STATE_BOUND_TOP) {
isInteraction = true;
- app.setFgInteractionTime(0);
- } else if (app.getCurProcState() <= PROCESS_STATE_FOREGROUND_SERVICE) {
- if (app.getFgInteractionTime() == 0) {
- app.setFgInteractionTime(nowElapsed);
+ state.setFgInteractionTime(0);
+ } else if (state.getCurProcState() <= PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (state.getFgInteractionTime() == 0) {
+ state.setFgInteractionTime(nowElapsed);
isInteraction = false;
} else {
- isInteraction = nowElapsed > app.getFgInteractionTime()
+ isInteraction = nowElapsed > state.getFgInteractionTime()
+ mConstants.SERVICE_USAGE_INTERACTION_TIME;
}
} else {
isInteraction =
- app.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
- app.setFgInteractionTime(0);
+ state.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
+ state.setFgInteractionTime(0);
}
if (isInteraction
- && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime())
- > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
- app.setInteractionEventTime(nowElapsed);
+ && (!state.hasReportedInteraction()
+ || (nowElapsed - state.getInteractionEventTime())
+ > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
+ state.setInteractionEventTime(nowElapsed);
String[] packages = app.getPackageList();
if (packages != null) {
for (int i = 0; i < packages.length; i++) {
@@ -2681,16 +2759,16 @@
}
}
}
- app.reportedInteraction = isInteraction;
+ state.setReportedInteraction(isInteraction);
if (!isInteraction) {
- app.setInteractionEventTime(0);
+ state.setInteractionEventTime(0);
}
}
- private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
- if (app.setProcState <= PROCESS_STATE_TOP
- && app.getCurProcState() > PROCESS_STATE_TOP) {
- app.lastTopTime = nowUptime;
+ private void maybeUpdateLastTopTime(ProcessStateRecord state, long nowUptime) {
+ if (state.getSetProcState() <= PROCESS_STATE_TOP
+ && state.getCurProcState() > PROCESS_STATE_TOP) {
+ state.setLastTopTime(nowUptime);
}
}
@@ -2712,13 +2790,15 @@
}
for (int i = N - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
- final long bgTime = uidRec.lastBackgroundTime;
- if (bgTime > 0 && !uidRec.idle) {
+ final long bgTime = uidRec.getLastBackgroundTime();
+ if (bgTime > 0 && !uidRec.isIdle()) {
if (bgTime <= maxBgTime) {
- EventLogTags.writeAmUidIdle(uidRec.uid);
- uidRec.idle = true;
- uidRec.setIdle = true;
- mService.doStopUidLocked(uidRec.uid, uidRec);
+ EventLogTags.writeAmUidIdle(uidRec.getUid());
+ synchronized (mProcLock) {
+ uidRec.setIdle(true);
+ uidRec.setSetIdle(true);
+ }
+ mService.doStopUidLocked(uidRec.getUid(), uidRec);
} else {
if (nextTime == 0 || nextTime > bgTime) {
nextTime = bgTime;
@@ -2736,35 +2816,35 @@
}
}
- @GuardedBy("mService")
- void setAppIdTempAllowlistStateLocked(int uid, boolean onAllowlist) {
+ @GuardedBy({"mService", "mProcLock"})
+ void setAppIdTempAllowlistStateLSP(int uid, boolean onAllowlist) {
boolean changed = false;
for (int i = mActiveUids.size() - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
- if (uidRec.uid == uid && uidRec.mCurAllowlist != onAllowlist) {
- uidRec.mCurAllowlist = onAllowlist;
+ if (uidRec.getUid() == uid && uidRec.isCurAllowListed() != onAllowlist) {
+ uidRec.setCurAllowListed(onAllowlist);
changed = true;
}
}
if (changed) {
- updateOomAdjLocked(OOM_ADJ_REASON_ALLOWLIST);
+ updateOomAdjLSP(OOM_ADJ_REASON_ALLOWLIST);
}
}
- @GuardedBy("mService")
- void setUidTempAllowlistStateLocked(int uid, boolean onAllowlist) {
+ @GuardedBy({"mService", "mProcLock"})
+ void setUidTempAllowlistStateLSP(int uid, boolean onAllowlist) {
boolean changed = false;
final UidRecord uidRec = mActiveUids.get(uid);
- if (uidRec != null && uidRec.mCurAllowlist != onAllowlist) {
- uidRec.mCurAllowlist = onAllowlist;
- updateOomAdjLocked(OOM_ADJ_REASON_ALLOWLIST);
+ if (uidRec != null && uidRec.isCurAllowListed() != onAllowlist) {
+ uidRec.setCurAllowListed(onAllowlist);
+ updateOomAdjLSP(OOM_ADJ_REASON_ALLOWLIST);
}
}
@GuardedBy("mService")
void dumpProcessListVariablesLocked(ProtoOutputStream proto) {
proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq);
- proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq);
+ proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.getLruSeqLOSP());
proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS,
mNumNonCachedProcs);
proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
@@ -2775,19 +2855,19 @@
@GuardedBy("mService")
void dumpSequenceNumbersLocked(PrintWriter pw) {
- pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq);
+ pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.getLruSeqLOSP());
}
@GuardedBy("mService")
void dumpProcCountsLocked(PrintWriter pw) {
pw.println(" mNumNonCachedProcs=" + mNumNonCachedProcs
- + " (" + mProcessList.getLruSizeLocked() + " total)"
+ + " (" + mProcessList.getLruSizeLOSP() + " total)"
+ " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
+ " mNumServiceProcs=" + mNumServiceProcs
+ " mNewNumServiceProcs=" + mNewNumServiceProcs);
}
- @GuardedBy("mService")
+ @GuardedBy("mProcLock")
void dumpCachedAppOptimizerSettings(PrintWriter pw) {
mCachedAppOptimizer.dump(pw);
}
@@ -2797,22 +2877,25 @@
mCacheOomRanker.dump(pw);
}
- @GuardedBy("mService")
- void updateAppFreezeStateLocked(ProcessRecord app) {
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateAppFreezeStateLSP(ProcessRecord app) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
+ final ProcessCachedOptimizerRecord opt = app.mOptRecord;
// if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
- if (app.frozen && app.shouldNotFreeze) {
- mCachedAppOptimizer.unfreezeAppLocked(app);
+ if (opt.isFrozen() && opt.shouldNotFreeze()) {
+ mCachedAppOptimizer.unfreezeAppLSP(app);
}
+ final ProcessStateRecord state = app.mState;
// Use current adjustment when freezing, set adjustment when unfreezing.
- if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) {
- mCachedAppOptimizer.freezeAppAsync(app);
- } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) {
- mCachedAppOptimizer.unfreezeAppLocked(app);
+ if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()
+ && !opt.shouldNotFreeze()) {
+ mCachedAppOptimizer.freezeAppAsyncLSP(app);
+ } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ && opt.isFrozen()) {
+ mCachedAppOptimizer.unfreezeAppLSP(app);
}
}
}
diff --git a/services/core/java/com/android/server/am/PackageList.java b/services/core/java/com/android/server/am/PackageList.java
index 978bcb7..aa10d52 100644
--- a/services/core/java/com/android/server/am/PackageList.java
+++ b/services/core/java/com/android/server/am/PackageList.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.annotation.NonNull;
import android.content.pm.VersionedPackage;
import android.util.ArrayMap;
@@ -73,7 +74,7 @@
}
}
- void forEachPackage(Consumer<String> callback) {
+ void forEachPackage(@NonNull Consumer<String> callback) {
synchronized (this) {
for (int i = 0, size = mPkgList.size(); i < size; i++) {
callback.accept(mPkgList.keyAt(i));
@@ -81,7 +82,7 @@
}
}
- void forEachPackage(BiConsumer<String, ProcessStats.ProcessStateHolder> callback) {
+ void forEachPackage(@NonNull BiConsumer<String, ProcessStats.ProcessStateHolder> callback) {
synchronized (this) {
for (int i = 0, size = mPkgList.size(); i < size; i++) {
callback.accept(mPkgList.keyAt(i), mPkgList.valueAt(i));
@@ -89,7 +90,16 @@
}
}
- <R> R forEachPackage(Function<String, R> callback) {
+ /**
+ * Search in the package list, invoke the given {@code callback} with each of the package names
+ * in that list; if the callback returns a non-null object, halt the search, return that
+ * object as the return value of this search function.
+ *
+ * @param callback The callback interface to accept the current package name; if it returns
+ * a non-null object, the search will be halted and this object will be used
+ * as the return value of this search function.
+ */
+ <R> R searchEachPackage(@NonNull Function<String, R> callback) {
synchronized (this) {
for (int i = 0, size = mPkgList.size(); i < size; i++) {
R r = callback.apply(mPkgList.keyAt(i));
@@ -101,7 +111,7 @@
return null;
}
- void forEachPackageProcessStats(Consumer<ProcessStats.ProcessStateHolder> callback) {
+ void forEachPackageProcessStats(@NonNull Consumer<ProcessStats.ProcessStateHolder> callback) {
synchronized (this) {
for (int i = 0, size = mPkgList.size(); i < size; i++) {
callback.accept(mPkgList.valueAt(i));
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 631b632..0eb48f6 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -24,7 +24,6 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
@@ -65,7 +64,8 @@
boolean canceled = false;
/**
* Map IBinder to duration specified as Pair<Long, Integer>, Long is allowlist duration in
- * milliseconds, Integer is allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+ * milliseconds, Integer is allowlist type defined at
+ * {@link android.os.PowerWhitelistManager.TempAllowListType}
*/
private ArrayMap<IBinder, Pair<Long, Integer>> mWhitelistDuration;
private RemoteCallbackList<IResultReceiver> mCancelCallbacks;
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index 37b1741..4f3438fe 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -148,13 +148,14 @@
@GuardedBy({"mLock", "mService.mPidsSelfLocked"})
private void lookForPhantomProcessesLocked(ProcessRecord app) {
- if (app.appZygote || app.killed || app.killedByAm) {
+ if (app.appZygote || app.isKilled() || app.isKilledByAm()) {
// process forked from app zygote doesn't have its own acct entry
return;
}
- InputStream input = mCgroupProcsFds.get(app.pid);
+ final int appPid = app.getPid();
+ InputStream input = mCgroupProcsFds.get(appPid);
if (input == null) {
- final String path = getCgroupFilePath(app.info.uid, app.pid);
+ final String path = getCgroupFilePath(app.info.uid, appPid);
try {
input = mInjector.openCgroupProcs(path);
} catch (FileNotFoundException | SecurityException e) {
@@ -164,7 +165,7 @@
return;
}
// Keep the FD open for better performance
- mCgroupProcsFds.put(app.pid, input);
+ mCgroupProcsFds.put(appPid, input);
}
final byte[] buf = mDataBuffer;
try {
@@ -180,7 +181,7 @@
for (int i = 0; i < read; i++) {
final byte b = buf[i];
if (b == '\n') {
- addChildPidLocked(app, pid);
+ addChildPidLocked(app, pid, appPid);
pid = 0;
} else {
pid = pid * 10 + (b - '0');
@@ -193,14 +194,14 @@
}
} while (true);
if (pid != 0) {
- addChildPidLocked(app, pid);
+ addChildPidLocked(app, pid, appPid);
}
// rewind the fd for the next read
input.skip(-totalRead);
} catch (IOException e) {
Slog.e(TAG, "Error in reading cgroup procs from " + app, e);
IoUtils.closeQuietly(input);
- mCgroupProcsFds.delete(app.pid);
+ mCgroupProcsFds.delete(appPid);
}
}
@@ -232,8 +233,8 @@
}
@GuardedBy({"mLock", "mService.mPidsSelfLocked"})
- private void addChildPidLocked(final ProcessRecord app, final int pid) {
- if (app.pid != pid) {
+ private void addChildPidLocked(final ProcessRecord app, final int pid, final int appPid) {
+ if (appPid != pid) {
// That's something else...
final ProcessRecord r = mService.mPidsSelfLocked.get(pid);
if (r != null) {
@@ -328,15 +329,16 @@
if (r != null) {
// It's a phantom process, bookkeep it
try {
+ final int appPid = r.getPid();
final PhantomProcessRecord proc = new PhantomProcessRecord(
- processName, uid, pid, r.pid, mService,
+ processName, uid, pid, appPid, mService,
this::onPhantomProcessKilledLocked);
proc.mUpdateSeq = mUpdateSeq;
mPhantomProcesses.put(pid, proc);
- SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(r.pid);
+ SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(appPid);
if (array == null) {
array = new SparseArray<>();
- mAppPhantomProcessMap.put(r.pid, array);
+ mAppPhantomProcessMap.put(appPid, array);
}
array.put(pid, proc);
if (proc.mPidFd != null) {
@@ -414,40 +416,42 @@
* order of the oom adjs of their parent process.
*/
void trimPhantomProcessesIfNecessary() {
- synchronized (mLock) {
- mTrimPhantomProcessScheduled = false;
- if (mService.mConstants.MAX_PHANTOM_PROCESSES < mPhantomProcesses.size()) {
- for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
- mTempPhantomProcesses.add(mPhantomProcesses.valueAt(i));
+ synchronized (mService.mProcLock) {
+ synchronized (mLock) {
+ mTrimPhantomProcessScheduled = false;
+ if (mService.mConstants.MAX_PHANTOM_PROCESSES < mPhantomProcesses.size()) {
+ for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
+ mTempPhantomProcesses.add(mPhantomProcesses.valueAt(i));
+ }
+ synchronized (mService.mPidsSelfLocked) {
+ Collections.sort(mTempPhantomProcesses, (a, b) -> {
+ final ProcessRecord ra = mService.mPidsSelfLocked.get(a.mPpid);
+ if (ra == null) {
+ // parent is gone, this process should have been killed too
+ return 1;
+ }
+ final ProcessRecord rb = mService.mPidsSelfLocked.get(b.mPpid);
+ if (rb == null) {
+ // parent is gone, this process should have been killed too
+ return -1;
+ }
+ if (ra.mState.getCurAdj() != rb.mState.getCurAdj()) {
+ return ra.mState.getCurAdj() - rb.mState.getCurAdj();
+ }
+ if (a.mKnownSince != b.mKnownSince) {
+ // In case of identical oom adj, younger one first
+ return a.mKnownSince < b.mKnownSince ? 1 : -1;
+ }
+ return 0;
+ });
+ }
+ for (int i = mTempPhantomProcesses.size() - 1;
+ i >= mService.mConstants.MAX_PHANTOM_PROCESSES; i--) {
+ final PhantomProcessRecord proc = mTempPhantomProcesses.get(i);
+ proc.killLocked("Trimming phantom processes", true);
+ }
+ mTempPhantomProcesses.clear();
}
- synchronized (mService.mPidsSelfLocked) {
- Collections.sort(mTempPhantomProcesses, (a, b) -> {
- final ProcessRecord ra = mService.mPidsSelfLocked.get(a.mPpid);
- if (ra == null) {
- // parent is gone, this process should have been killed too
- return 1;
- }
- final ProcessRecord rb = mService.mPidsSelfLocked.get(b.mPpid);
- if (rb == null) {
- // parent is gone, this process should have been killed too
- return -1;
- }
- if (ra.curAdj != rb.curAdj) {
- return ra.curAdj - rb.curAdj;
- }
- if (a.mKnownSince != b.mKnownSince) {
- // In case of identical oom adj, younger one first
- return a.mKnownSince < b.mKnownSince ? 1 : -1;
- }
- return 0;
- });
- }
- for (int i = mTempPhantomProcesses.size() - 1;
- i >= mService.mConstants.MAX_PHANTOM_PROCESSES; i--) {
- final PhantomProcessRecord proc = mTempPhantomProcesses.get(i);
- proc.killLocked("Trimming phantom processes", true);
- }
- mTempPhantomProcesses.clear();
}
}
}
@@ -498,7 +502,7 @@
}
}
// Lastly, kill the parent process too
- app.kill("Caused by child process: " + msg, reasonCode, subReason, true);
+ app.killLocked("Caused by child process: " + msg, reasonCode, subReason, true);
}
/**
@@ -508,7 +512,7 @@
void forEachPhantomProcessOfApp(final ProcessRecord app,
final Function<PhantomProcessRecord, Boolean> callback) {
synchronized (mLock) {
- int index = mAppPhantomProcessMap.indexOfKey(app.pid);
+ int index = mAppPhantomProcessMap.indexOfKey(app.getPid());
if (index >= 0) {
final SparseArray<PhantomProcessRecord> array =
mAppPhantomProcessMap.valueAt(index);
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
new file mode 100644
index 0000000..4643610
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -0,0 +1,157 @@
+/*
+ * 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.am;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * The state info of app when it's cached, used by the optimizer.
+ */
+final class ProcessCachedOptimizerRecord {
+ private final ProcessRecord mApp;
+
+ private final ActivityManagerGlobalLock mProcLock;
+
+ /**
+ * The last time that this process was compacted.
+ */
+ @GuardedBy("mProcLock")
+ private long mLastCompactTime;
+
+ /**
+ * The most recent compaction action requested for this app.
+ */
+ @GuardedBy("mProcLock")
+ private int mReqCompactAction;
+
+ /**
+ * The most recent compaction action performed for this app.
+ */
+ @GuardedBy("mProcLock")
+ private int mLastCompactAction;
+
+ /**
+ * True when the process is frozen.
+ */
+ @GuardedBy("mProcLock")
+ private boolean mFrozen;
+
+ /**
+ * An override on the freeze state is in progress.
+ */
+ @GuardedBy("mProcLock")
+ boolean mFreezerOverride;
+
+ /**
+ * Last time the app was (un)frozen, 0 for never.
+ */
+ @GuardedBy("mProcLock")
+ private long mFreezeUnfreezeTime;
+
+ /**
+ * True if a process has a WPRI binding from an unfrozen process.
+ */
+ @GuardedBy("mProcLock")
+ private boolean mShouldNotFreeze;
+
+ @GuardedBy("mProcLock")
+ long getLastCompactTime() {
+ return mLastCompactTime;
+ }
+
+ @GuardedBy("mProcLock")
+ void setLastCompactTime(long lastCompactTime) {
+ mLastCompactTime = lastCompactTime;
+ }
+
+ @GuardedBy("mProcLock")
+ int getReqCompactAction() {
+ return mReqCompactAction;
+ }
+
+ @GuardedBy("mProcLock")
+ void setReqCompactAction(int reqCompactAction) {
+ mReqCompactAction = reqCompactAction;
+ }
+
+ @GuardedBy("mProcLock")
+ int getLastCompactAction() {
+ return mLastCompactAction;
+ }
+
+ @GuardedBy("mProcLock")
+ void setLastCompactAction(int lastCompactAction) {
+ mLastCompactAction = lastCompactAction;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean isFrozen() {
+ return mFrozen;
+ }
+
+ @GuardedBy("mProcLock")
+ void setFrozen(boolean frozen) {
+ mFrozen = frozen;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean hasFreezerOverride() {
+ return mFreezerOverride;
+ }
+
+ @GuardedBy("mProcLock")
+ void setFreezerOverride(boolean freezerOverride) {
+ mFreezerOverride = freezerOverride;
+ }
+
+ @GuardedBy("mProcLock")
+ long getFreezeUnfreezeTime() {
+ return mFreezeUnfreezeTime;
+ }
+
+ @GuardedBy("mProcLock")
+ void setFreezeUnfreezeTime(long freezeUnfreezeTime) {
+ mFreezeUnfreezeTime = freezeUnfreezeTime;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean shouldNotFreeze() {
+ return mShouldNotFreeze;
+ }
+
+ @GuardedBy("mProcLock")
+ void setShouldNotFreeze(boolean shouldNotFreeze) {
+ mShouldNotFreeze = shouldNotFreeze;
+ }
+
+ ProcessCachedOptimizerRecord(ProcessRecord app) {
+ mApp = app;
+ mProcLock = app.mService.mProcLock;
+ }
+
+ void init(long nowUptime) {
+ mFreezeUnfreezeTime = nowUptime;
+ }
+
+ @GuardedBy("mProcLock")
+ void dump(PrintWriter pw, String prefix, long nowUptime) {
+ pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime);
+ pw.print(" lastCompactAction="); pw.println(mLastCompactAction);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
new file mode 100644
index 0000000..64e307a
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -0,0 +1,555 @@
+/*
+ * 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.am;
+
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
+import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.am.ProcessRecord.TAG;
+
+import android.app.ActivityManager;
+import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IncrementalStatesInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.MemoryPressureUtil;
+import com.android.server.wm.WindowProcessController;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+
+/**
+ * The error state of the process, such as if it's crashing/ANR etc.
+ */
+class ProcessErrorStateRecord {
+ final ProcessRecord mApp;
+ private final ActivityManagerService mService;
+
+ private final ActivityManagerGlobalLock mProcLock;
+
+ /**
+ * True if disabled in the bad process list.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mBad;
+
+ /**
+ * Are we in the process of crashing?
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mCrashing;
+
+ /**
+ * Suppress normal auto-dismiss of crash dialog & report UI?
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mForceCrashReport;
+
+ /**
+ * Does the app have a not responding dialog?
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mNotResponding;
+
+ /**
+ * The report about crash of the app, generated & stored when an app gets into a crash.
+ * Will be "null" when all is OK.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private ActivityManager.ProcessErrorStateInfo mCrashingReport;
+
+ /**
+ * The report about ANR of the app, generated & stored when an app gets into an ANR.
+ * Will be "null" when all is OK.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private ActivityManager.ProcessErrorStateInfo mNotRespondingReport;
+
+ /**
+ * Controller for error dialogs.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private final ErrorDialogController mDialogController;
+
+ /**
+ * Who will be notified of the error. This is usually an activity in the
+ * app that installed the package.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private ComponentName mErrorReportReceiver;
+
+ /**
+ * Optional local handler to be invoked in the process crash.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private Runnable mCrashHandler;
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isBad() {
+ return mBad;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setBad(boolean bad) {
+ mBad = bad;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isCrashing() {
+ return mCrashing;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCrashing(boolean crashing) {
+ mCrashing = crashing;
+ mApp.getWindowProcessController().setCrashing(crashing);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isForceCrashReport() {
+ return mForceCrashReport;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setForceCrashReport(boolean forceCrashReport) {
+ mForceCrashReport = forceCrashReport;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isNotResponding() {
+ return mNotResponding;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setNotResponding(boolean notResponding) {
+ mNotResponding = notResponding;
+ mApp.getWindowProcessController().setNotResponding(notResponding);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ Runnable getCrashHandler() {
+ return mCrashHandler;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCrashHandler(Runnable crashHandler) {
+ mCrashHandler = crashHandler;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ActivityManager.ProcessErrorStateInfo getCrashingReport() {
+ return mCrashingReport;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCrashingReport(ActivityManager.ProcessErrorStateInfo crashingReport) {
+ mCrashingReport = crashingReport;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ActivityManager.ProcessErrorStateInfo getNotRespondingReport() {
+ return mNotRespondingReport;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setNotRespondingReport(ActivityManager.ProcessErrorStateInfo notRespondingReport) {
+ mNotRespondingReport = notRespondingReport;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ComponentName getErrorReportReceiver() {
+ return mErrorReportReceiver;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setErrorReportReceiver(ComponentName errorReportReceiver) {
+ mErrorReportReceiver = errorReportReceiver;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ErrorDialogController getDialogController() {
+ return mDialogController;
+ }
+
+ ProcessErrorStateRecord(ProcessRecord app) {
+ mApp = app;
+ mService = app.mService;
+ mProcLock = mService.mProcLock;
+ mDialogController = new ErrorDialogController(app);
+ }
+
+ void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
+ String parentShortComponentName, WindowProcessController parentProcess,
+ boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
+ ArrayList<Integer> firstPids = new ArrayList<>(5);
+ SparseArray<Boolean> lastPids = new SparseArray<>(20);
+
+ mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
+ synchronized (mService) {
+ mApp.killLocked("anr", ApplicationExitInfo.REASON_ANR, true);
+ }
+ });
+
+ long anrTime = SystemClock.uptimeMillis();
+ if (isMonitorCpuUsage()) {
+ mService.updateCpuStatsNow();
+ }
+
+ final boolean isSilentAnr;
+ final int pid = mApp.getPid();
+ synchronized (mService) {
+ // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
+ if (mService.mAtmInternal.isShuttingDown()) {
+ Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
+ return;
+ } else if (isNotResponding()) {
+ Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
+ return;
+ } else if (isCrashing()) {
+ Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
+ return;
+ } else if (mApp.isKilledByAm()) {
+ Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
+ return;
+ } else if (mApp.isKilled()) {
+ Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
+ return;
+ }
+
+ // In case we come through here for the same app before completing
+ // this one, mark as anring now so we will bail out.
+ synchronized (mProcLock) {
+ setNotResponding(true);
+ }
+
+ // Log the ANR to the event log.
+ EventLog.writeEvent(EventLogTags.AM_ANR, mApp.userId, pid, mApp.processName,
+ mApp.info.flags, annotation);
+
+ // Dump thread traces as quickly as we can, starting with "interesting" processes.
+ firstPids.add(pid);
+
+ // Don't dump other PIDs if it's a background ANR or is requested to only dump self.
+ isSilentAnr = isSilentAnr();
+ if (!isSilentAnr && !onlyDumpSelf) {
+ int parentPid = pid;
+ if (parentProcess != null && parentProcess.getPid() > 0) {
+ parentPid = parentProcess.getPid();
+ }
+ if (parentPid != pid) firstPids.add(parentPid);
+
+ if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
+
+ final int ppid = parentPid;
+ mService.mProcessList.forEachLruProcessesLOSP(false, r -> {
+ if (r != null && r.getThread() != null) {
+ int myPid = r.getPid();
+ if (myPid > 0 && myPid != pid && myPid != ppid && myPid != MY_PID) {
+ if (r.isPersistent()) {
+ firstPids.add(myPid);
+ if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
+ } else if (r.mServices.isTreatedLikeActivity()) {
+ firstPids.add(myPid);
+ if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
+ } else {
+ lastPids.put(myPid, Boolean.TRUE);
+ if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ // Check if package is still being loaded
+ boolean isPackageLoading = false;
+ final PackageManagerInternal packageManagerInternal = mService.getPackageManagerInternal();
+ if (aInfo != null && aInfo.packageName != null) {
+ IncrementalStatesInfo incrementalStatesInfo =
+ packageManagerInternal.getIncrementalStatesInfo(
+ aInfo.packageName, mApp.uid, mApp.userId);
+ if (incrementalStatesInfo != null) {
+ isPackageLoading = incrementalStatesInfo.isLoading();
+ }
+ }
+
+ // Log the ANR to the main log.
+ StringBuilder info = new StringBuilder();
+ info.setLength(0);
+ info.append("ANR in ").append(mApp.processName);
+ if (activityShortComponentName != null) {
+ info.append(" (").append(activityShortComponentName).append(")");
+ }
+ info.append("\n");
+ info.append("PID: ").append(pid).append("\n");
+ if (annotation != null) {
+ info.append("Reason: ").append(annotation).append("\n");
+ }
+ if (parentShortComponentName != null
+ && parentShortComponentName.equals(activityShortComponentName)) {
+ info.append("Parent: ").append(parentShortComponentName).append("\n");
+ }
+
+ if (isPackageLoading) {
+ // Report in the main log that the package is still loading
+ final float loadingProgress = packageManagerInternal.getIncrementalStatesInfo(
+ aInfo.packageName, mApp.uid, mApp.userId).getProgress();
+ info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
+ }
+
+ StringBuilder report = new StringBuilder();
+ report.append(MemoryPressureUtil.currentPsiState());
+ ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
+
+ // don't dump native PIDs for background ANRs unless it is the process of interest
+ String[] nativeProcs = null;
+ if (isSilentAnr || onlyDumpSelf) {
+ for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
+ if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
+ nativeProcs = new String[] { mApp.processName };
+ break;
+ }
+ }
+ } else {
+ nativeProcs = NATIVE_STACKS_OF_INTEREST;
+ }
+
+ int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
+ ArrayList<Integer> nativePids = null;
+
+ if (pids != null) {
+ nativePids = new ArrayList<>(pids.length);
+ for (int i : pids) {
+ nativePids.add(i);
+ }
+ }
+
+ // For background ANRs, don't pass the ProcessCpuTracker to
+ // avoid spending 1/2 second collecting stats to rank lastPids.
+ StringWriter tracesFileException = new StringWriter();
+ // To hold the start and end offset to the ANR trace file respectively.
+ final long[] offsets = new long[2];
+ File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+ isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
+ nativePids, tracesFileException, offsets);
+
+ if (isMonitorCpuUsage()) {
+ mService.updateCpuStatsNow();
+ mService.mAppProfiler.printCurrentCpuState(report, anrTime);
+ info.append(processCpuTracker.printCurrentLoad());
+ info.append(report);
+ }
+ report.append(tracesFileException.getBuffer());
+
+ info.append(processCpuTracker.printCurrentState(anrTime));
+
+ Slog.e(TAG, info.toString());
+ if (tracesFile == null) {
+ // There is no trace file, so dump (only) the alleged culprit's threads to the log
+ Process.sendSignal(pid, Process.SIGNAL_QUIT);
+ } else if (offsets[1] > 0) {
+ // We've dumped into the trace file successfully
+ mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
+ pid, mApp.uid, mApp.getPackageList(), tracesFile, offsets[0], offsets[1]);
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.ANR_OCCURRED, mApp.uid, mApp.processName,
+ activityShortComponentName == null ? "unknown" : activityShortComponentName,
+ annotation,
+ (mApp.info != null) ? (mApp.info.isInstantApp()
+ ? FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__TRUE
+ : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__FALSE)
+ : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE,
+ mApp.isInterestingToUserLocked()
+ ? FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND
+ : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
+ mApp.getProcessClassEnum(),
+ (mApp.info != null) ? mApp.info.packageName : "", isPackageLoading);
+ final ProcessRecord parentPr = parentProcess != null
+ ? (ProcessRecord) parentProcess.mOwner : null;
+ mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
+ parentShortComponentName, parentPr, annotation, report.toString(), tracesFile,
+ null);
+
+ if (mApp.getWindowProcessController().appNotResponding(info.toString(),
+ () -> {
+ synchronized (mService) {
+ mApp.killLocked("anr", ApplicationExitInfo.REASON_ANR, true);
+ }
+ },
+ () -> {
+ synchronized (mService) {
+ mService.mServices.scheduleServiceTimeoutLocked(mApp);
+ }
+ })) {
+ return;
+ }
+
+ synchronized (mService) {
+ // mBatteryStatsService can be null if the AMS is constructed with injector only. This
+ // will only happen in tests.
+ if (mService.mBatteryStatsService != null) {
+ mService.mBatteryStatsService.noteProcessAnr(mApp.processName, mApp.uid);
+ }
+
+ if (isSilentAnr() && !mApp.isDebugging()) {
+ mApp.killLocked("bg anr", ApplicationExitInfo.REASON_ANR, true);
+ return;
+ }
+
+ synchronized (mProcLock) {
+ // Set the app's notResponding state, and look up the errorReportReceiver
+ makeAppNotRespondingLSP(activityShortComponentName,
+ annotation != null ? "ANR " + annotation : "ANR", info.toString());
+ }
+
+ // Notify package manager service to possibly update package state
+ if (aInfo != null && aInfo.packageName != null) {
+ packageManagerInternal.notifyPackageCrashOrAnr(aInfo.packageName);
+ }
+
+ // mUiHandler can be null if the AMS is constructed with injector only. This will only
+ // happen in tests.
+ if (mService.mUiHandler != null) {
+ // Bring up the infamous App Not Responding dialog
+ Message msg = Message.obtain();
+ msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
+ msg.obj = new AppNotRespondingDialog.Data(mApp, aInfo, aboveSystem);
+
+ mService.mUiHandler.sendMessage(msg);
+ }
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg) {
+ setNotResponding(true);
+ // mAppErrors can be null if the AMS is constructed with injector only. This will only
+ // happen in tests.
+ if (mService.mAppErrors != null) {
+ mNotRespondingReport = mService.mAppErrors.generateProcessError(mApp,
+ ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+ activity, shortMsg, longMsg, null);
+ }
+ startAppProblemLSP();
+ mApp.getWindowProcessController().stopFreezingActivities();
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void startAppProblemLSP() {
+ // If this app is not running under the current user, then we can't give it a report button
+ // because that would require launching the report UI under a different user.
+ mErrorReportReceiver = null;
+
+ for (int userId : mService.mUserController.getCurrentProfileIds()) {
+ if (mApp.userId == userId) {
+ mErrorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
+ mService.mContext, mApp.info.packageName, mApp.info.flags);
+ }
+ }
+ mService.skipCurrentReceiverLocked(mApp);
+ }
+
+ @GuardedBy("mService")
+ private boolean isInterestingForBackgroundTraces() {
+ // The system_server is always considered interesting.
+ if (mApp.getPid() == MY_PID) {
+ return true;
+ }
+
+ // A package is considered interesting if any of the following is true :
+ //
+ // - It's displaying an activity.
+ // - It's the SystemUI.
+ // - It has an overlay or a top UI visible.
+ //
+ // NOTE: The check whether a given ProcessRecord belongs to the systemui
+ // process is a bit of a kludge, but the same pattern seems repeated at
+ // several places in the system server.
+ return mApp.isInterestingToUserLocked()
+ || (mApp.info != null && "com.android.systemui".equals(mApp.info.packageName))
+ || (mApp.mState.hasTopUi() || mApp.mState.hasOverlayUi());
+ }
+
+ private boolean getShowBackground() {
+ return Settings.Secure.getInt(mService.mContext.getContentResolver(),
+ Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
+ }
+
+ /**
+ * Unless configured otherwise, swallow ANRs in background processes & kill the process.
+ * Non-private access is for tests only.
+ */
+ @VisibleForTesting
+ @GuardedBy("mService")
+ boolean isSilentAnr() {
+ return !getShowBackground() && !isInterestingForBackgroundTraces();
+ }
+
+ /** Non-private access is for tests only. */
+ @VisibleForTesting
+ boolean isMonitorCpuUsage() {
+ return mService.mAppProfiler.MONITOR_CPU_USAGE;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void onCleanupApplicationRecordLSP() {
+ // Dismiss any open dialogs.
+ getDialogController().clearAllErrorDialogs();
+
+ setCrashing(false);
+ setNotResponding(false);
+ }
+
+ void dump(PrintWriter pw, String prefix, long nowUptime) {
+ synchronized (mProcLock) {
+ if (mCrashing || mDialogController.hasCrashDialogs() || mNotResponding
+ || mDialogController.hasAnrDialogs() || mBad) {
+ pw.print(prefix);
+ pw.print(" mCrashing=" + mCrashing);
+ pw.print(" " + mDialogController.getCrashDialogs());
+ pw.print(" mNotResponding=" + mNotResponding);
+ pw.print(" " + mDialogController.getAnrDialogs());
+ pw.print(" bad=" + mBad);
+
+ // mCrashing or mNotResponding is always set before errorReportReceiver
+ if (mErrorReportReceiver != null) {
+ pw.print(" errorReportReceiver=");
+ pw.print(mErrorReportReceiver.flattenToShortString());
+ }
+ pw.println();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a768532..0576345 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -55,6 +55,7 @@
import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
import static com.android.server.am.AppProfiler.TAG_PSS;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
@@ -114,6 +115,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ProcessMap;
@@ -149,6 +151,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Activity manager code dealing with processes.
@@ -372,6 +376,13 @@
private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
/**
+ * Enable automatic zero-initialization of native heap memory allocations.
+ */
+ @ChangeId
+ @Disabled
+ private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
+
+ /**
* Enable sampled memory bug detection in the app.
* @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
*/
@@ -466,34 +477,41 @@
* List of running applications, sorted by recent usage.
* The first entry in the list is the least recently used.
*/
- final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
+ @CompositeRWLock({"mService", "mProcLock"})
+ private final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
/**
* Where in mLruProcesses that the processes hosting activities start.
*/
- int mLruProcessActivityStart = 0;
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mLruProcessActivityStart = 0;
/**
* Where in mLruProcesses that the processes hosting services start.
* This is after (lower index) than mLruProcessesActivityStart.
*/
- int mLruProcessServiceStart = 0;
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mLruProcessServiceStart = 0;
/**
* Current sequence id for process LRU updating.
*/
- int mLruSeq = 0;
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mLruSeq = 0;
+ @CompositeRWLock({"mService", "mProcLock"})
ActiveUids mActiveUids;
/**
* The currently running isolated processes.
*/
+ @GuardedBy("mService")
final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
/**
* The currently running application zygotes.
*/
+ @GuardedBy("mService")
final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
/**
@@ -505,6 +523,7 @@
/**
* The processes that are forked off an application zygote.
*/
+ @GuardedBy("mService")
final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
new ArrayMap<AppZygote, ArrayList<ProcessRecord>>();
@@ -532,6 +551,8 @@
*/
private final int[] mZygoteSigChldMessage = new int[3];
+ ActivityManagerGlobalLock mProcLock;
+
final class IsolatedUidRange {
@VisibleForTesting
public final int mFirstUid;
@@ -640,6 +661,7 @@
* The available isolated UIDs for processes that are not spawned from an application zygote.
*/
@VisibleForTesting
+ @GuardedBy("mService")
IsolatedUidRange mGlobalIsolatedUids = new IsolatedUidRange(Process.FIRST_ISOLATED_UID,
Process.LAST_ISOLATED_UID);
@@ -647,6 +669,7 @@
* An allocator for isolated UID ranges for apps that use an application zygote.
*/
@VisibleForTesting
+ @GuardedBy("mService")
IsolatedUidRangeAllocator mAppIsolatedUidRangeAllocator =
new IsolatedUidRangeAllocator(Process.FIRST_APP_ZYGOTE_ISOLATED_UID,
Process.LAST_APP_ZYGOTE_ISOLATED_UID, Process.NUM_UIDS_PER_APP_ZYGOTE);
@@ -654,6 +677,7 @@
/**
* Processes that are being forcibly torn down.
*/
+ @GuardedBy("mService")
final ArrayList<ProcessRecord> mRemovedProcesses = new ArrayList<ProcessRecord>();
// Self locked with the inner lock within the RemoteCallbackList
@@ -674,14 +698,14 @@
*/
private final Object mProcessChangeLock = new Object();
-
/**
* All of the applications we currently have running organized by name.
* The keys are strings of the application package name (as
* returned by the package manager), and the keys are ApplicationRecord
* objects.
*/
- final MyProcessMap mProcessNames = new MyProcessMap();
+ @CompositeRWLock({"mService", "mProcLock"})
+ private final MyProcessMap mProcessNames = new MyProcessMap();
final class MyProcessMap extends ProcessMap<ProcessRecord> {
@Override
@@ -749,6 +773,7 @@
mService = service;
mActiveUids = activeUids;
mPlatformCompat = platformCompat;
+ mProcLock = service.mProcLock;
// Get this after boot, and won't be changed until it's rebooted, as we don't
// want some apps enabled while some apps disabled
mAppDataIsolationEnabled =
@@ -842,13 +867,13 @@
*/
Map<Integer, String> getProcessesWithPendingBindMounts(int userId) {
final Map<Integer, String> pidPackageMap = new HashMap<>();
- synchronized (mService) {
+ synchronized (mProcLock) {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord record = mLruProcesses.get(i);
- if (record.userId != userId || !record.bindMountPending) {
+ if (record.userId != userId || !record.isBindMountPending()) {
continue;
}
- final int pid = record.pid;
+ final int pid = record.getPid();
// It can happen when app process is starting, but zygote work is not done yet so
// system does not this pid record yet.
if (pid == 0) {
@@ -857,8 +882,8 @@
}
pidPackageMap.put(pid, record.info.packageName);
}
- return pidPackageMap;
}
+ return pidPackageMap;
}
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
@@ -1534,6 +1559,7 @@
}
}
+ @GuardedBy("mService")
final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean
keepIfLarge) {
if (uid == SYSTEM_UID) {
@@ -1554,21 +1580,19 @@
}
ProcessRecord proc = mProcessNames.get(processName, uid);
if (false && proc != null && !keepIfLarge
- && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY
+ && proc.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY
&& proc.mProfile.getLastCachedPss() >= 4000) {
// Turn this condition on to cause killing to happen regularly, for testing.
- final long lastCachedPss;
synchronized (mService.mAppProfiler.mProfilerLock) {
proc.mProfile.reportCachedKill();
- lastCachedPss = proc.mProfile.getLastCachedPss();
}
- proc.kill(Long.toString(lastCachedPss) + "k from cached",
+ proc.killLocked(Long.toString(proc.mProfile.getLastCachedPss()) + "k from cached",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_LARGE_CACHED,
true);
} else if (proc != null && !keepIfLarge
&& !mService.mAppProfiler.isLastMemoryLevelNormal()
- && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+ && proc.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
final long lastCachedPss;
boolean doKilling = false;
synchronized (mService.mAppProfiler.mProfilerLock) {
@@ -1582,7 +1606,7 @@
Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + lastCachedPss);
}
if (doKilling) {
- proc.kill(Long.toString(lastCachedPss) + "k from cached",
+ proc.killLocked(Long.toString(lastCachedPss) + "k from cached",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_LARGE_CACHED,
true);
@@ -1604,14 +1628,16 @@
outInfo.foregroundAppThreshold = getMemLevel(FOREGROUND_APP_ADJ);
}
- ProcessRecord findAppProcessLocked(IBinder app, String reason) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ProcessRecord findAppProcessLOSP(IBinder app, String reason) {
final int NP = mProcessNames.getMap().size();
for (int ip = 0; ip < NP; ip++) {
SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
final int NA = apps.size();
for (int ia = 0; ia < NA; ia++) {
ProcessRecord p = apps.valueAt(ia);
- if (p.thread != null && p.thread.asBinder() == app) {
+ final IApplicationThread thread = p.getThread();
+ if (thread != null && thread.asBinder() == app) {
return p;
}
}
@@ -1685,12 +1711,28 @@
return gidArray;
}
- // Returns the memory tagging level to be enabled. If memory tagging isn't
- // requested, returns zero.
- private int getMemtagLevel(ProcessRecord app) {
- // Ensure the hardware + kernel actually supports MTE.
- if (!Zygote.nativeSupportsMemoryTagging()) {
- return 0;
+ private int memtagModeToZygoteMemtagLevel(int memtagMode) {
+ switch (memtagMode) {
+ case ApplicationInfo.MEMTAG_ASYNC:
+ return Zygote.MEMORY_TAG_LEVEL_ASYNC;
+ case ApplicationInfo.MEMTAG_SYNC:
+ return Zygote.MEMORY_TAG_LEVEL_SYNC;
+ default:
+ return Zygote.MEMORY_TAG_LEVEL_NONE;
+ }
+ }
+
+ // Returns the requested memory tagging level.
+ private int getRequestedMemtagLevel(ProcessRecord app) {
+ // Look at the process attribute first.
+ if (app.processInfo != null
+ && app.processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(app.processInfo.memtagMode);
+ }
+
+ // Then at the application attribute.
+ if (app.info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(app.info.getMemtagMode());
}
if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_SYNC, app.info)) {
@@ -1701,40 +1743,43 @@
return Zygote.MEMORY_TAG_LEVEL_ASYNC;
}
- return 0;
- }
-
- private boolean shouldEnableTaggedPointers(ProcessRecord app) {
- // Ensure we have platform + kernel support for TBI.
- if (!Zygote.nativeSupportsTaggedPointers()) {
- return false;
- }
-
// Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
if (!app.info.allowsNativeHeapPointerTagging()) {
- return false;
+ return Zygote.MEMORY_TAG_LEVEL_NONE;
}
// Check to see that the compat feature for TBI is enabled.
- if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
- return false;
- }
-
- return true;
- }
-
- private int decideTaggingLevel(ProcessRecord app) {
- // Check MTE support first, as it should take precedence over TBI.
- int memtagLevel = getMemtagLevel(app);
- if (memtagLevel != 0) {
- return memtagLevel;
- }
-
- if (shouldEnableTaggedPointers(app)) {
+ if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
return Zygote.MEMORY_TAG_LEVEL_TBI;
}
- return 0;
+ return Zygote.MEMORY_TAG_LEVEL_NONE;
+ }
+
+ private int decideTaggingLevel(ProcessRecord app) {
+ // Get the desired tagging level (app manifest + compat features).
+ int level = getRequestedMemtagLevel(app);
+
+ // Take into account the hardware capabilities.
+ if (Zygote.nativeSupportsMemoryTagging()) {
+ // MTE devices can not do TBI, because the Zygote process already has live MTE
+ // allocations. Downgrade TBI to NONE.
+ if (level == Zygote.MEMORY_TAG_LEVEL_TBI) {
+ level = Zygote.MEMORY_TAG_LEVEL_NONE;
+ }
+ } else if (Zygote.nativeSupportsTaggedPointers()) {
+ // TBI-but-not-MTE devices downgrade MTE modes to TBI.
+ // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
+ // the "fake" pointer tagging (TBI).
+ if (level == Zygote.MEMORY_TAG_LEVEL_ASYNC || level == Zygote.MEMORY_TAG_LEVEL_SYNC) {
+ level = Zygote.MEMORY_TAG_LEVEL_TBI;
+ }
+ } else {
+ // Otherwise disable all tagging.
+ level = Zygote.MEMORY_TAG_LEVEL_NONE;
+ }
+
+ return level;
}
private int decideGwpAsanLevel(ProcessRecord app) {
@@ -1745,7 +1790,7 @@
? Zygote.GWP_ASAN_LEVEL_ALWAYS
: Zygote.GWP_ASAN_LEVEL_NEVER;
}
- // Then at the applicaton attribute.
+ // Then at the application attribute.
if (app.info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
return app.info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
? Zygote.GWP_ASAN_LEVEL_ALWAYS
@@ -1762,25 +1807,40 @@
return Zygote.GWP_ASAN_LEVEL_NEVER;
}
+ private boolean enableNativeHeapZeroInit(ProcessRecord app) {
+ // Look at the process attribute first.
+ if (app.processInfo != null && app.processInfo.nativeHeapZeroInit != null) {
+ return app.processInfo.nativeHeapZeroInit;
+ }
+ // Then at the application attribute.
+ if (app.info.isNativeHeapZeroInit() != null) {
+ return app.info.isNativeHeapZeroInit();
+ }
+ // Compat feature last.
+ if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_ZERO_INIT, app.info)) {
+ return true;
+ }
+ return false;
+ }
+
/**
* @return {@code true} if process start is successful, false otherwise.
*/
@GuardedBy("mService")
boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
- int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
- String abiOverride) {
- if (app.pendingStart) {
+ int zygotePolicyFlags, boolean disableHiddenApiChecks, String abiOverride) {
+ if (app.isPendingStart()) {
return true;
}
long startTime = SystemClock.uptimeMillis();
- if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) {
+ if (app.getPid() > 0 && app.getPid() != ActivityManagerService.MY_PID) {
checkSlow(startTime, "startProcess: removing from pids map");
- mService.removePidLocked(app);
- app.bindMountPending = false;
+ mService.removePidLocked(app.getPid(), app);
+ app.setBindMountPending(false);
mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
checkSlow(startTime, "startProcess: done removing from pids map");
app.setPid(0);
- app.startSeq = 0;
+ app.setStartSeq(0);
}
if (DEBUG_PROCESSES && mService.mProcessesOnHold.contains(app)) Slog.v(
@@ -1835,7 +1895,7 @@
gids = computeGidsForProcess(mountExternal, uid, permGids);
}
- app.mountMode = mountExternal;
+ app.setMountMode(mountExternal);
checkSlow(startTime, "startProcess: building args");
if (mService.mAtmInternal.isFactoryTestProcess(app.getWindowProcessController())) {
uid = 0;
@@ -1914,10 +1974,6 @@
throw new IllegalStateException("Invalid API policy: " + policy);
}
runtimeFlags |= policyBits;
-
- if (disableTestApiChecks) {
- runtimeFlags |= Zygote.DISABLE_TEST_API_ENFORCEMENT_POLICY;
- }
}
String useAppImageCache = SystemProperties.get(
@@ -1955,9 +2011,9 @@
instructionSet = VMRuntime.getInstructionSet(requiredAbi);
}
- app.gids = gids;
+ app.setGids(gids);
app.setRequiredAbi(requiredAbi);
- app.instructionSet = instructionSet;
+ app.setInstructionSet(instructionSet);
// If instructionSet is non-null, this indicates that the system_server is spawning a
// process with an ISA that may be different from its own. System (kernel and hardware)
@@ -1973,6 +2029,10 @@
runtimeFlags |= decideTaggingLevel(app);
}
+ if (enableNativeHeapZeroInit(app)) {
+ runtimeFlags |= Zygote.NATIVE_HEAP_ZERO_INIT;
+ }
+
// the per-user SELinux context must be set
if (TextUtils.isEmpty(app.info.seInfoUser)) {
Slog.wtf(ActivityManagerService.TAG, "SELinux tag not defined",
@@ -2008,23 +2068,26 @@
int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
long startTime) {
- app.pendingStart = true;
- app.killedByAm = false;
- app.removed = false;
- app.killed = false;
- if (app.startSeq != 0) {
- Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
- + " with non-zero startSeq:" + app.startSeq);
+ app.setPendingStart(true);
+ app.setRemoved(false);
+ synchronized (mProcLock) {
+ app.setKilledByAm(false);
+ app.setKilled(false);
}
- if (app.pid != 0) {
+ if (app.getStartSeq() != 0) {
Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
- + " with non-zero pid:" + app.pid);
+ + " with non-zero startSeq:" + app.getStartSeq());
}
- app.mDisabledCompatChanges = null;
+ if (app.getPid() != 0) {
+ Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
+ + " with non-zero pid:" + app.getPid());
+ }
+ app.setDisabledCompatChanges(null);
if (mPlatformCompat != null) {
- app.mDisabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info);
+ app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info));
}
- final long startSeq = app.startSeq = ++mProcStartSeqCounter;
+ final long startSeq = ++mProcStartSeqCounter;
+ app.setStartSeq(startSeq);
app.setStartParams(uid, hostingRecord, seInfo, startTime);
app.setUsingWrapper(invokeWith != null
|| Zygote.getWrapProperty(app.processName) != null);
@@ -2048,11 +2111,11 @@
} catch (RuntimeException e) {
Slog.e(ActivityManagerService.TAG, "Failure starting process "
+ app.processName, e);
- app.pendingStart = false;
+ app.setPendingStart(false);
mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
false, false, true, false, false, app.userId, "start failure");
}
- return app.pid > 0;
+ return app.getPid() > 0;
}
}
@@ -2065,11 +2128,11 @@
final int[] gids, final int runtimeFlags, int zygotePolicyFlags,
final int mountExternal, final String requiredAbi, final String instructionSet,
final String invokeWith, final long startSeq) {
- // If there is a precede instance of the process, wait for its death with a timeout.
+ // If there is a preceding instance of the process, wait for its death with a timeout.
// Use local reference since we are not using locks here
- final ProcessRecord precedence = app.mPrecedence;
- if (precedence != null) {
- final int pid = precedence.pid;
+ final ProcessRecord predecessor = app.mPredecessor;
+ if (predecessor != null) {
+ final int pid = predecessor.getPid();
long now = System.currentTimeMillis();
final long end = now + PROC_KILL_TIMEOUT;
final int oldPolicy = StrictMode.getThreadPolicyMask();
@@ -2077,34 +2140,34 @@
StrictMode.setThreadPolicyMask(0);
Process.waitForProcessDeath(pid, PROC_KILL_TIMEOUT);
// It's killed successfully, but we'd make sure the cleanup work is done.
- synchronized (precedence) {
- if (app.mPrecedence != null) {
+ synchronized (predecessor) {
+ if (app.mPredecessor != null) {
now = System.currentTimeMillis();
if (now < end) {
try {
- precedence.wait(end - now);
+ predecessor.wait(end - now);
} catch (InterruptedException e) {
}
}
}
- if (app.mPrecedence != null) {
+ if (app.mPredecessor != null) {
// The cleanup work hasn't be done yet, let's log it and continue.
- Slog.w(TAG, precedence + " has died, but its cleanup isn't done");
+ Slog.w(TAG, predecessor + " has died, but its cleanup isn't done");
}
}
} catch (Exception e) {
// It's still alive... maybe blocked at uninterruptible sleep ?
- Slog.wtf(TAG, precedence.toString() + " refused to die, but we need to launch "
+ Slog.wtf(TAG, predecessor.toString() + " refused to die, but we need to launch "
+ app, e);
} finally {
StrictMode.setThreadPolicyMask(oldPolicy);
}
}
try {
- final Process.ProcessStartResult startResult = startProcess(app.hostingRecord,
- entryPoint, app, app.startUid, gids, runtimeFlags, zygotePolicyFlags,
- mountExternal, app.seInfo, requiredAbi, instructionSet, invokeWith,
- app.startTime);
+ final Process.ProcessStartResult startResult = startProcess(app.getHostingRecord(),
+ entryPoint, app, app.getStartUid(), gids, runtimeFlags, zygotePolicyFlags,
+ mountExternal, app.getSeInfo(), requiredAbi, instructionSet, invokeWith,
+ app.getStartTime());
synchronized (mService) {
handleProcessStartedLocked(app, startResult, startSeq);
@@ -2114,7 +2177,7 @@
Slog.e(ActivityManagerService.TAG, "Failure starting process "
+ app.processName, e);
mPendingStarts.remove(startSeq);
- app.pendingStart = false;
+ app.setPendingStart(false);
mService.forceStopPackageLocked(app.info.packageName,
UserHandle.getAppId(app.uid),
false, false, true, false, false, app.userId, "start failure");
@@ -2140,19 +2203,19 @@
// Free the isolated uid for this process
final IsolatedUidRange appUidRange =
mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(app.info.processName,
- app.hostingRecord.getDefiningUid());
+ app.getHostingRecord().getDefiningUid());
if (appUidRange != null) {
appUidRange.freeIsolatedUidLocked(app.uid);
}
final AppZygote appZygote = mAppZygotes.get(app.info.processName,
- app.hostingRecord.getDefiningUid());
+ app.getHostingRecord().getDefiningUid());
if (appZygote != null) {
ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote);
zygoteProcesses.remove(app);
if (zygoteProcesses.size() == 0) {
mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG);
- if (app.removed) {
+ if (app.isRemoved()) {
// If we stopped this process because the package hosting it was removed,
// there's no point in delaying the app zygote kill.
killAppZygoteIfNeededLocked(appZygote, false /* force */);
@@ -2169,7 +2232,7 @@
synchronized (mService) {
// The UID for the app zygote should be the UID of the application hosting
// the service.
- final int uid = app.hostingRecord.getDefiningUid();
+ final int uid = app.getHostingRecord().getDefiningUid();
AppZygote appZygote = mAppZygotes.get(app.info.processName, uid);
final ArrayList<ProcessRecord> zygoteProcessList;
if (appZygote == null) {
@@ -2178,7 +2241,7 @@
}
final IsolatedUidRange uidRange =
mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(
- app.info.processName, app.hostingRecord.getDefiningUid());
+ app.info.processName, app.getHostingRecord().getDefiningUid());
final int userId = UserHandle.getUserId(uid);
// Create the app-zygote and provide it with the UID-range it's allowed
// to setresuid/setresgid to.
@@ -2191,7 +2254,7 @@
// packageName and uid of the defining application. This is because the
// preloading only makes sense in the context of the defining application,
// not the calling one.
- appInfo.packageName = app.hostingRecord.getDefiningPackageName();
+ appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
appInfo.uid = uid;
appZygote = new AppZygote(appInfo, uid, firstUid, lastUid);
mAppZygotes.put(app.info.processName, uid, appZygote);
@@ -2238,14 +2301,15 @@
private boolean needsStorageDataIsolation(StorageManagerInternal storageManagerInternal,
ProcessRecord app) {
+ final int mountMode = app.getMountMode();
return mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid)
&& !storageManagerInternal.isExternalStorageService(app.uid)
// Special mounting mode doesn't need to have data isolation as they won't
// access /mnt/user anyway.
- && app.mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
- && app.mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH
- && app.mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER
- && app.mountMode != Zygote.MOUNT_EXTERNAL_NONE;
+ && mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
+ && mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH
+ && mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER
+ && mountMode != Zygote.MOUNT_EXTERNAL_NONE;
}
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
@@ -2261,7 +2325,7 @@
// Use has-foreground-activities as a temporary hint so the current scheduling
// group won't be lost when the process is attaching. The actual state will be
// refreshed when computing oom-adj.
- app.setHasForegroundActivities(true);
+ app.mState.setHasForegroundActivities(true);
}
Map<String, Pair<String, Long>> pkgDataInfoMap;
@@ -2311,7 +2375,7 @@
app.processName)) {
// Cannot prepare Android/app and Android/obb directory or inode == 0,
// so we won't mount it in zygote, but resume the mount after unlocking device.
- app.bindMountPending = true;
+ app.setBindMountPending(true);
bindMountAppStorageDirs = false;
}
}
@@ -2328,8 +2392,9 @@
startResult = startWebView(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
- app.info.dataDir, null, app.info.packageName, app.mDisabledCompatChanges,
- new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+ app.info.dataDir, null, app.info.packageName,
+ app.getDisabledCompatChanges(),
+ new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else if (hostingRecord.usesAppZygote()) {
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
@@ -2339,17 +2404,17 @@
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
- app.mDisabledCompatChanges, pkgDataInfoMap, allowlistedAppDataInfoMap,
+ app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
false, false,
- new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+ new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
- isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
+ isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap,
allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
- new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+ new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
}
checkSlow(startTime, "startProcess: returned from zygote!");
return startResult;
@@ -2364,15 +2429,14 @@
}
@GuardedBy("mService")
- final boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
+ boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
int zygotePolicyFlags, String abiOverride) {
return startProcessLocked(app, hostingRecord, zygotePolicyFlags,
- false /* disableHiddenApiChecks */, false /* disableTestApiChecks */,
- abiOverride);
+ false /* disableHiddenApiChecks */, abiOverride);
}
@GuardedBy("mService")
- final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
+ ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
boolean keepIfLarge, String abiOverride, String entryPoint, String[] entryPointArgs,
@@ -2405,7 +2469,7 @@
info.processName);
mService.mAppErrors.clearBadProcess(processName, info.uid);
if (app != null) {
- app.bad = false;
+ app.mErrorState.setBad(false);
}
}
}
@@ -2422,11 +2486,11 @@
// already running.
if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "startProcess: name=" + processName
+ " app=" + app + " knownToBeDead=" + knownToBeDead
- + " thread=" + (app != null ? app.thread : null)
- + " pid=" + (app != null ? app.pid : -1));
- ProcessRecord precedence = null;
- if (app != null && app.pid > 0) {
- if ((!knownToBeDead && !app.killed) || app.thread == null) {
+ + " thread=" + (app != null ? app.getThread() : null)
+ + " pid=" + (app != null ? app.getPid() : -1));
+ ProcessRecord predecessor = null;
+ if (app != null && app.getPid() > 0) {
+ if ((!knownToBeDead && !app.isKilled()) || app.getThread() == null) {
// We already have the app running, or are waiting for it to
// come up (we have a pid but not yet its thread), so keep it.
if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App already running: " + app);
@@ -2440,12 +2504,12 @@
// clean it up now.
if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App died: " + app);
checkSlow(startTime, "startProcess: bad proc running, killing");
- ProcessList.killProcessGroup(app.uid, app.pid);
+ ProcessList.killProcessGroup(app.uid, app.getPid());
checkSlow(startTime, "startProcess: done killing old proc");
- if (!app.killed
+ if (!app.isKilled()
|| mService.mAppProfiler.isLastMemoryLevelNormal()
- || app.setProcState < ActivityManager.PROCESS_STATE_CACHED_EMPTY
+ || app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_CACHED_EMPTY
|| app.mProfile.getLastCachedPss() < getCachedRestoreThresholdKb()) {
// Throw a wtf if it's not killed, or killed but not because the system was in
// memory pressure + the app was in "cch-empty" and used large amount of memory
@@ -2454,8 +2518,8 @@
Slog.w(TAG_PROCESSES, app.toString() + " is attached to a previous process");
}
// We are not going to re-use the ProcessRecord, as we haven't dealt with the cleanup
- // routine of it yet, but we'd set it as the precedence of the new process.
- precedence = app;
+ // routine of it yet, but we'd set it as the predecessor of the new process.
+ predecessor = app;
app = null;
}
@@ -2467,12 +2531,12 @@
+ processName + "/" + info.uid + " isolated=" + isolated);
return null;
}
- app.crashHandler = crashHandler;
- app.isolatedEntryPoint = entryPoint;
- app.isolatedEntryPointArgs = entryPointArgs;
- if (precedence != null) {
- app.mPrecedence = precedence;
- precedence.mSuccessor = app;
+ app.mErrorState.setCrashHandler(crashHandler);
+ app.setIsolatedEntryPoint(entryPoint);
+ app.setIsolatedEntryPointArgs(entryPointArgs);
+ if (predecessor != null) {
+ app.mPredecessor = predecessor;
+ predecessor.mSuccessor = app;
}
checkSlow(startTime, "startProcess: done creating new process record");
} else {
@@ -2505,7 +2569,7 @@
@GuardedBy("mService")
private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
StringBuilder sb = null;
- if (app.killedByAm) {
+ if (app.isKilledByAm()) {
if (sb == null) sb = new StringBuilder();
sb.append("killedByAm=true;");
}
@@ -2513,13 +2577,13 @@
if (sb == null) sb = new StringBuilder();
sb.append("No entry in mProcessNames;");
}
- if (!app.pendingStart) {
+ if (!app.isPendingStart()) {
if (sb == null) sb = new StringBuilder();
sb.append("pendingStart=false;");
}
- if (app.startSeq > expectedStartSeq) {
+ if (app.getStartSeq() > expectedStartSeq) {
if (sb == null) sb = new StringBuilder();
- sb.append("seq=" + app.startSeq + ",expected=" + expectedStartSeq + ";");
+ sb.append("seq=" + app.getStartSeq() + ",expected=" + expectedStartSeq + ";");
}
try {
AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, app.userId);
@@ -2542,7 +2606,7 @@
Process.ProcessStartResult startResult, long expectedStartSeq) {
// Indicates that this process start has been taken care of.
if (mPendingStarts.get(expectedStartSeq) == null) {
- if (pending.pid == startResult.pid) {
+ if (pending.getPid() == startResult.pid) {
pending.setUsingWrapper(startResult.usingWrapper);
// TODO: Update already existing clients of usingWrapper
}
@@ -2561,31 +2625,31 @@
Slog.w(TAG_PROCESSES, app + " start not valid, killing pid=" +
pid
+ ", " + reason);
- app.pendingStart = false;
+ app.setPendingStart(false);
killProcessQuiet(pid);
- Process.killProcessGroup(app.uid, app.pid);
+ Process.killProcessGroup(app.uid, app.getPid());
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_START, reason);
return false;
}
mService.mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
- checkSlow(app.startTime, "startProcess: done updating battery stats");
+ checkSlow(app.getStartTime(), "startProcess: done updating battery stats");
EventLog.writeEvent(EventLogTags.AM_PROC_START,
- UserHandle.getUserId(app.startUid), pid, app.startUid,
- app.processName, app.hostingRecord.getType(),
- app.hostingRecord.getName() != null ? app.hostingRecord.getName() : "");
+ UserHandle.getUserId(app.getStartUid()), pid, app.getStartUid(),
+ app.processName, app.getHostingRecord().getType(),
+ app.getHostingRecord().getName() != null ? app.getHostingRecord().getName() : "");
try {
AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.info.packageName,
- app.processName, app.uid, app.seInfo, app.info.sourceDir, pid);
+ app.processName, app.uid, app.getSeInfo(), app.info.sourceDir, pid);
} catch (RemoteException ex) {
// Ignore
}
Watchdog.getInstance().processStarted(app.processName, pid);
- checkSlow(app.startTime, "startProcess: building log message");
+ checkSlow(app.getStartTime(), "startProcess: building log message");
StringBuilder buf = mStringBuilder;
buf.setLength(0);
buf.append("Start proc ");
@@ -2593,23 +2657,25 @@
buf.append(':');
buf.append(app.processName);
buf.append('/');
- UserHandle.formatUid(buf, app.startUid);
- if (app.isolatedEntryPoint != null) {
+ UserHandle.formatUid(buf, app.getStartUid());
+ if (app.getIsolatedEntryPoint() != null) {
buf.append(" [");
- buf.append(app.isolatedEntryPoint);
+ buf.append(app.getIsolatedEntryPoint());
buf.append("]");
}
buf.append(" for ");
- buf.append(app.hostingRecord.getType());
- if (app.hostingRecord.getName() != null) {
+ buf.append(app.getHostingRecord().getType());
+ if (app.getHostingRecord().getName() != null) {
buf.append(" ");
- buf.append(app.hostingRecord.getName());
+ buf.append(app.getHostingRecord().getName());
}
- mService.reportUidInfoMessageLocked(TAG, buf.toString(), app.startUid);
- app.setPid(pid);
- app.setUsingWrapper(usingWrapper);
- app.pendingStart = false;
- checkSlow(app.startTime, "startProcess: starting to update pids map");
+ mService.reportUidInfoMessageLocked(TAG, buf.toString(), app.getStartUid());
+ synchronized (mProcLock) {
+ app.setPid(pid);
+ app.setUsingWrapper(usingWrapper);
+ app.setPendingStart(false);
+ }
+ checkSlow(app.getStartTime(), "startProcess: starting to update pids map");
ProcessRecord oldApp;
synchronized (mService.mPidsSelfLocked) {
oldApp = mService.mPidsSelfLocked.get(pid);
@@ -2618,11 +2684,11 @@
if (oldApp != null && !app.isolated) {
// Clean up anything relating to this pid first
Slog.wtf(TAG, "handleProcessStartedLocked process:" + app.processName
- + " startSeq:" + app.startSeq
+ + " startSeq:" + app.getStartSeq()
+ " pid:" + pid
+ " belongs to another existing app:" + oldApp.processName
- + " startSeq:" + oldApp.startSeq);
- mService.cleanUpApplicationRecordLocked(oldApp, false, false, -1,
+ + " startSeq:" + oldApp.getStartSeq());
+ mService.cleanUpApplicationRecordLocked(oldApp, pid, false, false, -1,
true /*replacingPid*/);
}
mService.addPidLocked(app);
@@ -2634,43 +2700,46 @@
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
}
- checkSlow(app.startTime, "startProcess: done updating pids map");
+ checkSlow(app.getStartTime(), "startProcess: done updating pids map");
return true;
}
- final void removeLruProcessLocked(ProcessRecord app) {
+ @GuardedBy("mService")
+ void removeLruProcessLocked(ProcessRecord app) {
int lrui = mLruProcesses.lastIndexOf(app);
if (lrui >= 0) {
- if (!app.killed) {
- if (app.isPersistent()) {
- Slog.w(TAG, "Removing persistent process that hasn't been killed: " + app);
- } else {
- Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
- if (app.pid > 0) {
- killProcessQuiet(app.pid);
- ProcessList.killProcessGroup(app.uid, app.pid);
- noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_REMOVE_LRU, "hasn't been killed");
+ synchronized (mProcLock) {
+ if (!app.isKilled()) {
+ if (app.isPersistent()) {
+ Slog.w(TAG, "Removing persistent process that hasn't been killed: " + app);
} else {
- app.pendingStart = false;
+ Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
+ if (app.getPid() > 0) {
+ killProcessQuiet(app.getPid());
+ ProcessList.killProcessGroup(app.uid, app.getPid());
+ noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_REMOVE_LRU, "hasn't been killed");
+ } else {
+ app.setPendingStart(false);
+ }
}
}
+ if (lrui < mLruProcessActivityStart) {
+ mLruProcessActivityStart--;
+ }
+ if (lrui < mLruProcessServiceStart) {
+ mLruProcessServiceStart--;
+ }
+ mLruProcesses.remove(lrui);
}
- if (lrui < mLruProcessActivityStart) {
- mLruProcessActivityStart--;
- }
- if (lrui < mLruProcessServiceStart) {
- mLruProcessServiceStart--;
- }
- mLruProcesses.remove(lrui);
- mService.removeOomAdjTargetLocked(app, true);
}
+ mService.removeOomAdjTargetLocked(app, true);
}
- @GuardedBy("mService")
- boolean killPackageProcessesLocked(String packageName, int appId, int userId, int minOomAdj,
+ @GuardedBy({"mService", "mProcLock"})
+ boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj,
int reasonCode, int subReason, String reason) {
- return killPackageProcessesLocked(packageName, appId, userId, minOomAdj,
+ return killPackageProcessesLSP(packageName, appId, userId, minOomAdj,
false /* callerWillRestart */, true /* allowRestart */, true /* doit */,
false /* evenPersistent */, false /* setRemoved */, reasonCode,
subReason, reason);
@@ -2703,8 +2772,8 @@
}
}
- @GuardedBy("mService")
- final boolean killPackageProcessesLocked(String packageName, int appId,
+ @GuardedBy({"mService", "mProcLock"})
+ boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode,
int subReason, String reason) {
@@ -2723,7 +2792,7 @@
// we don't kill persistent processes
continue;
}
- if (app.removed) {
+ if (app.isRemoved()) {
if (doit) {
procs.add(app);
}
@@ -2731,7 +2800,7 @@
}
// Skip process if it doesn't meet our oom adj requirement.
- if (app.setAdj < minOomAdj) {
+ if (app.mState.getSetAdj() < minOomAdj) {
// Note it is still possible to have a process with oom adj 0 in the killed
// processes, but it does not mean misjudgment. E.g. a bound service process
// and its client activity process are both in the background, so they are
@@ -2753,8 +2822,8 @@
// that match it. We need to qualify this by the processes
// that are running under the specified app and user ID.
} else {
- final boolean isDep = app.pkgDeps != null
- && app.pkgDeps.contains(packageName);
+ final boolean isDep = app.getPkgDeps() != null
+ && app.getPkgDeps().contains(packageName);
if (!isDep && UserHandle.getAppId(app.uid) != appId) {
continue;
}
@@ -2771,7 +2840,7 @@
return true;
}
if (setRemoved) {
- app.removed = true;
+ app.setRemoved(true);
}
procs.add(app);
}
@@ -2812,12 +2881,12 @@
mService.mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
boolean needRestart = false;
- if ((app.pid > 0 && app.pid != ActivityManagerService.MY_PID) || (app.pid == 0 && app
- .pendingStart)) {
- int pid = app.pid;
+ final int pid = app.getPid();
+ if ((pid > 0 && pid != ActivityManagerService.MY_PID)
+ || (pid == 0 && app.isPendingStart())) {
if (pid > 0) {
- mService.removePidLocked(app);
- app.bindMountPending = false;
+ mService.removePidLocked(pid, app);
+ app.setBindMountPending(false);
mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
if (app.isolated) {
@@ -2833,8 +2902,8 @@
needRestart = true;
}
}
- app.kill(reason, reasonCode, subReason, true);
- mService.handleAppDiedLocked(app, willRestart, allowRestart);
+ app.killLocked(reason, reasonCode, subReason, true);
+ mService.handleAppDiedLocked(app, pid, willRestart, allowRestart);
if (willRestart) {
removeLruProcessLocked(app);
mService.addAppLocked(app.info, null, false, null /* ABI override */,
@@ -2848,48 +2917,51 @@
}
@GuardedBy("mService")
- final void addProcessNameLocked(ProcessRecord proc) {
+ void addProcessNameLocked(ProcessRecord proc) {
// We shouldn't already have a process under this name, but just in case we
// need to clean up whatever may be there now.
- ProcessRecord old = removeProcessNameLocked(proc.processName, proc.uid);
- if (old == proc && proc.isPersistent()) {
- // We are re-adding a persistent process. Whatevs! Just leave it there.
- Slog.w(TAG, "Re-adding persistent process " + proc);
- } else if (old != null) {
- if (old.killed) {
- // The old process has been killed, we probably haven't had
- // a chance to clean up the old record, just log a warning
- Slog.w(TAG, "Existing proc " + old + " was killed "
- + (SystemClock.uptimeMillis() - old.mKillTime)
- + "ms ago when adding " + proc);
- } else {
- Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc);
+ synchronized (mProcLock) {
+ ProcessRecord old = removeProcessNameLocked(proc.processName, proc.uid);
+ if (old == proc && proc.isPersistent()) {
+ // We are re-adding a persistent process. Whatevs! Just leave it there.
+ Slog.w(TAG, "Re-adding persistent process " + proc);
+ } else if (old != null) {
+ if (old.isKilled()) {
+ // The old process has been killed, we probably haven't had
+ // a chance to clean up the old record, just log a warning
+ Slog.w(TAG, "Existing proc " + old + " was killed "
+ + (SystemClock.uptimeMillis() - old.getKillTime())
+ + "ms ago when adding " + proc);
+ } else {
+ Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc);
+ }
}
- }
- UidRecord uidRec = mActiveUids.get(proc.uid);
- if (uidRec == null) {
- uidRec = new UidRecord(proc.uid);
- // This is the first appearance of the uid, report it now!
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "Creating new process uid: " + uidRec);
- if (Arrays.binarySearch(mService.mDeviceIdleTempAllowlist,
- UserHandle.getAppId(proc.uid)) >= 0
- || mService.mPendingTempAllowlist.indexOfKey(proc.uid) >= 0) {
- uidRec.mSetAllowlist = uidRec.mCurAllowlist = true;
+ UidRecord uidRec = mActiveUids.get(proc.uid);
+ if (uidRec == null) {
+ uidRec = new UidRecord(proc.uid, mService);
+ // This is the first appearance of the uid, report it now!
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "Creating new process uid: " + uidRec);
+ }
+ if (Arrays.binarySearch(mService.mDeviceIdleTempAllowlist,
+ UserHandle.getAppId(proc.uid)) >= 0
+ || mService.mPendingTempAllowlist.indexOfKey(proc.uid) >= 0) {
+ uidRec.setCurAllowListed(true);
+ uidRec.setSetAllowListed(true);
+ }
+ uidRec.updateHasInternetPermission();
+ mActiveUids.put(proc.uid, uidRec);
+ EventLogTags.writeAmUidRunning(uidRec.getUid());
+ mService.noteUidProcessState(uidRec.getUid(), uidRec.getCurProcState(),
+ uidRec.getCurCapability());
}
- uidRec.updateHasInternetPermission();
- mActiveUids.put(proc.uid, uidRec);
- EventLogTags.writeAmUidRunning(uidRec.uid);
- mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
- uidRec.curCapability);
- }
- proc.uidRecord = uidRec;
- uidRec.procRecords.add(proc);
+ proc.setUidRecord(uidRec);
+ uidRec.addProcess(proc);
- // Reset render thread tid if it was already set, so new process can set it again.
- proc.renderThreadTid = 0;
- uidRec.numProcs++;
- mProcessNames.put(proc.processName, proc.uid, proc);
+ // Reset render thread tid if it was already set, so new process can set it again.
+ proc.setRenderThreadTid(0);
+ mProcessNames.put(proc.processName, proc.uid, proc);
+ }
if (proc.isolated) {
mIsolatedProcesses.put(proc.uid, proc);
}
@@ -2908,7 +2980,7 @@
}
@GuardedBy("mService")
- final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
+ ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
boolean isolated, int isolatedUid, HostingRecord hostingRecord) {
String proc = customProcess != null ? customProcess : info.processName;
final int userId = UserHandle.getUserId(info.uid);
@@ -2942,58 +3014,63 @@
FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
}
final ProcessRecord r = new ProcessRecord(mService, info, proc, uid);
+ final ProcessStateRecord state = r.mState;
if (!mService.mBooted && !mService.mBooting
&& userId == UserHandle.USER_SYSTEM
&& (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
// The system process is initialized to SCHED_GROUP_DEFAULT in init.rc.
- r.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
- r.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+ state.setSetSchedGroup(ProcessList.SCHED_GROUP_DEFAULT);
r.setPersistent(true);
- r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
+ state.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
}
if (isolated && isolatedUid != 0) {
// Special case for startIsolatedProcess (internal only) - assume the process
// is required by the system server to prevent it being killed.
- r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
+ state.setMaxAdj(ProcessList.PERSISTENT_SERVICE_ADJ);
}
addProcessNameLocked(r);
return r;
}
@GuardedBy("mService")
- final ProcessRecord removeProcessNameLocked(final String name, final int uid) {
+ ProcessRecord removeProcessNameLocked(final String name, final int uid) {
return removeProcessNameLocked(name, uid, null);
}
@GuardedBy("mService")
- final ProcessRecord removeProcessNameLocked(final String name, final int uid,
+ ProcessRecord removeProcessNameLocked(final String name, final int uid,
final ProcessRecord expecting) {
ProcessRecord old = mProcessNames.get(name, uid);
- // Only actually remove when the currently recorded value matches the
- // record that we expected; if it doesn't match then we raced with a
- // newly created process and we don't want to destroy the new one.
- if ((expecting == null) || (old == expecting)) {
- mProcessNames.remove(name, uid);
- }
final ProcessRecord record = expecting != null ? expecting : old;
- if (record != null && record.uidRecord != null) {
- final UidRecord uidRecord = record.uidRecord;
- uidRecord.numProcs--;
- uidRecord.procRecords.remove(record);
- if (uidRecord.numProcs == 0) {
- // No more processes using this uid, tell clients it is gone.
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "No more processes in " + uidRecord);
- mService.enqueueUidChangeLocked(uidRecord, -1,
- UidRecord.CHANGE_GONE);
- EventLogTags.writeAmUidStopped(uid);
- mActiveUids.remove(uid);
- mService.mFgsStartTempAllowList.remove(record.info.uid);
- mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT,
- ActivityManager.PROCESS_CAPABILITY_NONE);
+ synchronized (mProcLock) {
+ // Only actually remove when the currently recorded value matches the
+ // record that we expected; if it doesn't match then we raced with a
+ // newly created process and we don't want to destroy the new one.
+ if ((expecting == null) || (old == expecting)) {
+ mProcessNames.remove(name, uid);
}
- record.uidRecord = null;
+ if (record != null) {
+ final UidRecord uidRecord = record.getUidRecord();
+ if (uidRecord != null) {
+ uidRecord.removeProcess(record);
+ if (uidRecord.getNumOfProcs() == 0) {
+ // No more processes using this uid, tell clients it is gone.
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "No more processes in " + uidRecord);
+ }
+ mService.enqueueUidChangeLocked(uidRecord, -1,
+ UidRecord.CHANGE_GONE);
+ EventLogTags.writeAmUidStopped(uid);
+ mActiveUids.remove(uid);
+ mService.mFgsStartTempAllowList.remove(record.info.uid);
+ mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT,
+ ActivityManager.PROCESS_CAPABILITY_NONE);
+ }
+ record.setUidRecord(null);
+ }
+ }
}
mIsolatedProcesses.remove(uid);
mGlobalIsolatedUids.freeIsolatedUidLocked(uid);
@@ -3006,13 +3083,14 @@
}
/** Call setCoreSettings on all LRU processes, with the new settings. */
- @GuardedBy("mService")
- void updateCoreSettingsLocked(Bundle settings) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void updateCoreSettingsLOSP(Bundle settings) {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord processRecord = mLruProcesses.get(i);
+ final IApplicationThread thread = processRecord.getThread();
try {
- if (processRecord.thread != null) {
- processRecord.thread.setCoreSettings(settings);
+ if (thread != null) {
+ thread.setCoreSettings(settings);
}
} catch (RemoteException re) {
/* ignore */
@@ -3026,8 +3104,8 @@
* @param minTargetSdk
* @param maxProcState
*/
- @GuardedBy("mService")
- void killAllBackgroundProcessesExceptLocked(int minTargetSdk, int maxProcState) {
+ @GuardedBy({"mService", "mProcLock"})
+ void killAllBackgroundProcessesExceptLSP(int minTargetSdk, int maxProcState) {
final ArrayList<ProcessRecord> procs = new ArrayList<>();
final int NP = mProcessNames.getMap().size();
for (int ip = 0; ip < NP; ip++) {
@@ -3035,8 +3113,9 @@
final int NA = apps.size();
for (int ia = 0; ia < NA; ia++) {
final ProcessRecord app = apps.valueAt(ia);
- if (app.removed || ((minTargetSdk < 0 || app.info.targetSdkVersion < minTargetSdk)
- && (maxProcState < 0 || app.setProcState > maxProcState))) {
+ if (app.isRemoved()
+ || ((minTargetSdk < 0 || app.info.targetSdkVersion < minTargetSdk)
+ && (maxProcState < 0 || app.mState.getSetProcState() > maxProcState))) {
procs.add(app);
}
}
@@ -3053,13 +3132,14 @@
* Call updateTimePrefs on all LRU processes
* @param timePref The time pref to pass to each process
*/
- @GuardedBy("mService")
- void updateAllTimePrefsLocked(int timePref) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void updateAllTimePrefsLOSP(int timePref) {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
- if (r.thread != null) {
+ final IApplicationThread thread = r.getThread();
+ if (thread != null) {
try {
- r.thread.updateTimePrefs(timePref);
+ thread.updateTimePrefs(timePref);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update preferences for: "
+ r.info.processName);
@@ -3070,15 +3150,16 @@
void setAllHttpProxy() {
// Update the HTTP proxy for each application thread.
- synchronized (mService) {
+ synchronized (mProcLock) {
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord r = mLruProcesses.get(i);
+ IApplicationThread thread = r.getThread();
// Don't dispatch to isolated processes as they can't access ConnectivityManager and
// don't have network privileges anyway. Exclude system server and update it
// separately outside the AMS lock, to avoid deadlock with Connectivity Service.
- if (r.pid != ActivityManagerService.MY_PID && r.thread != null && !r.isolated) {
+ if (r.getPid() != ActivityManagerService.MY_PID && thread != null && !r.isolated) {
try {
- r.thread.updateHttpProxy();
+ thread.updateHttpProxy();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update http proxy for: "
+ r.info.processName);
@@ -3089,13 +3170,14 @@
ActivityThread.updateHttpProxy(mService.mContext);
}
- @GuardedBy("mService")
- void clearAllDnsCacheLocked() {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void clearAllDnsCacheLOSP() {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
- if (r.thread != null) {
+ final IApplicationThread thread = r.getThread();
+ if (thread != null) {
try {
- r.thread.clearDnsCache();
+ thread.clearDnsCache();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName);
}
@@ -3103,13 +3185,14 @@
}
}
- @GuardedBy("mService")
- void handleAllTrustStorageUpdateLocked() {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void handleAllTrustStorageUpdateLOSP() {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
- if (r.thread != null) {
+ final IApplicationThread thread = r.getThread();
+ if (thread != null) {
try {
- r.thread.handleTrustStorageUpdate();
+ thread.handleTrustStorageUpdate();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to handle trust storage update for: " +
r.info.processName);
@@ -3118,10 +3201,10 @@
}
}
- @GuardedBy("mService")
- int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
+ @GuardedBy({"mService", "mProcLock"})
+ private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index,
int lruSeq, String what, Object obj, ProcessRecord srcApp) {
- app.lastActivityTime = now;
+ app.setLastActivityTime(now);
if (app.hasActivitiesOrRecentTasks()) {
// Don't want to touch dependent processes that are hosting activities.
@@ -3153,7 +3236,7 @@
if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
+ " in LRU list: " + app);
mLruProcesses.add(index, app);
- app.lruSeq = lruSeq;
+ app.setLruSeq(lruSeq);
return index;
}
@@ -3173,45 +3256,51 @@
* @param endIndex The current end of the top being processed. Typically topI - 1. That is,
* where we are going to start potentially adjusting other entries in the list.
*/
- private void updateClientActivitiesOrdering(final ProcessRecord topApp, final int topI,
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateClientActivitiesOrderingLSP(final ProcessRecord topApp, final int topI,
final int bottomI, int endIndex) {
- if (topApp.hasActivitiesOrRecentTasks() || topApp.treatLikeActivity
- || !topApp.hasClientActivities()) {
+ final ProcessServiceRecord topPsr = topApp.mServices;
+ if (topApp.hasActivitiesOrRecentTasks() || topPsr.isTreatedLikeActivity()
+ || !topPsr.hasClientActivities()) {
// If this is not a special process that has client activities, then there is
// nothing to do.
return;
}
final int uid = topApp.info.uid;
- if (topApp.connectionGroup > 0) {
- int endImportance = topApp.connectionImportance;
+ final int topConnectionGroup = topPsr.getConnectionGroup();
+ if (topConnectionGroup > 0) {
+ int endImportance = topPsr.getConnectionImportance();
for (int i = endIndex; i >= bottomI; i--) {
final ProcessRecord subProc = mLruProcesses.get(i);
+ final ProcessServiceRecord subPsr = subProc.mServices;
+ final int subConnectionGroup = subPsr.getConnectionGroup();
+ final int subConnectionImportance = subPsr.getConnectionImportance();
if (subProc.info.uid == uid
- && subProc.connectionGroup == topApp.connectionGroup) {
- if (i == endIndex && subProc.connectionImportance >= endImportance) {
+ && subConnectionGroup == topConnectionGroup) {
+ if (i == endIndex && subConnectionImportance >= endImportance) {
// This process is already in the group, and its importance
// is not as strong as the process before it, so keep it
// correctly positioned in the group.
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Keeping in-place above " + subProc
+ " endImportance=" + endImportance
- + " group=" + subProc.connectionGroup
- + " importance=" + subProc.connectionImportance);
+ + " group=" + subConnectionGroup
+ + " importance=" + subConnectionImportance);
endIndex--;
- endImportance = subProc.connectionImportance;
+ endImportance = subConnectionImportance;
} else {
// We want to pull this up to be with the rest of the group,
// and order within the group by importance.
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Pulling up " + subProc
+ " to position in group with importance="
- + subProc.connectionImportance);
+ + subConnectionImportance);
boolean moved = false;
for (int pos = topI; pos > endIndex; pos--) {
final ProcessRecord posProc = mLruProcesses.get(pos);
- if (subProc.connectionImportance
- <= posProc.connectionImportance) {
+ if (subConnectionImportance
+ <= posProc.mServices.getConnectionImportance()) {
mLruProcesses.remove(i);
mLruProcesses.add(pos, subProc);
if (DEBUG_LRU) Slog.d(TAG_LRU,
@@ -3232,7 +3321,7 @@
+ " from position " + i + " to end of group @ "
+ endIndex);
endIndex--;
- endImportance = subProc.connectionImportance;
+ endImportance = subConnectionImportance;
}
}
}
@@ -3244,6 +3333,8 @@
int i = endIndex;
while (i >= bottomI) {
ProcessRecord subProc = mLruProcesses.get(i);
+ final ProcessServiceRecord subPsr = subProc.mServices;
+ final int subConnectionGroup = subPsr.getConnectionGroup();
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Looking to spread old procs, at " + subProc + " @ " + i);
if (subProc.info.uid != uid) {
@@ -3266,7 +3357,8 @@
subProc = mLruProcesses.get(i);
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Looking at next app at " + i + ": " + subProc);
- if (subProc.hasActivitiesOrRecentTasks() || subProc.treatLikeActivity) {
+ if (subProc.hasActivitiesOrRecentTasks()
+ || subPsr.isTreatedLikeActivity()) {
if (DEBUG_LRU) Slog.d(TAG_LRU,
"This is hosting an activity!");
if (hasActivity) {
@@ -3276,7 +3368,7 @@
break;
}
hasActivity = true;
- } else if (subProc.hasClientActivities()) {
+ } else if (subPsr.hasClientActivities()) {
if (DEBUG_LRU) Slog.d(TAG_LRU,
"This is a client of an activity");
if (hasActivity) {
@@ -3287,21 +3379,21 @@
"Already found a different activity: connUid="
+ connUid + " uid=" + subProc.info.uid);
break;
- } else if (connGroup == 0 || connGroup != subProc.connectionGroup) {
+ } else if (connGroup == 0 || connGroup != subConnectionGroup) {
// Previously saw a different group or not from a group,
// want to treat these as different things.
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Already found a different group: connGroup="
- + connGroup + " group=" + subProc.connectionGroup);
+ + connGroup + " group=" + subConnectionGroup);
break;
}
} else {
if (DEBUG_LRU) Slog.d(TAG_LRU,
"This is an activity client! uid="
- + subProc.info.uid + " group=" + subProc.connectionGroup);
+ + subProc.info.uid + " group=" + subConnectionGroup);
hasActivity = true;
connUid = subProc.info.uid;
- connGroup = subProc.connectionGroup;
+ connGroup = subConnectionGroup;
}
}
endIndex--;
@@ -3322,13 +3414,16 @@
}
if (endIndex >= bottomI) {
final ProcessRecord endProc = mLruProcesses.get(endIndex);
+ final ProcessServiceRecord endPsr = endProc.mServices;
+ final int endConnectionGroup = endPsr.getConnectionGroup();
for (endIndex--; endIndex >= bottomI; endIndex--) {
final ProcessRecord nextEndProc = mLruProcesses.get(endIndex);
+ final int nextConnectionGroup = nextEndProc.mServices.getConnectionGroup();
if (nextEndProc.info.uid != uid
- || nextEndProc.connectionGroup != endProc.connectionGroup) {
+ || nextConnectionGroup != endConnectionGroup) {
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Found next group or app: " + nextEndProc + " @ "
- + endIndex + " group=" + nextEndProc.connectionGroup);
+ + endIndex + " group=" + nextConnectionGroup);
break;
}
}
@@ -3342,10 +3437,11 @@
}
}
- final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
- ProcessRecord client) {
- final boolean hasActivity = app.hasActivitiesOrRecentTasks() || app.hasClientActivities()
- || app.treatLikeActivity;
+ @GuardedBy("mService")
+ void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) {
+ final ProcessServiceRecord psr = app.mServices;
+ final boolean hasActivity = app.hasActivitiesOrRecentTasks() || psr.hasClientActivities()
+ || psr.isTreatedLikeActivity();
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
// The process has activities, so we are only allowing activity-based adjustments
@@ -3355,9 +3451,18 @@
return;
}
+ synchronized (mProcLock) {
+ updateLruProcessLSP(app, client, hasActivity, hasService);
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateLruProcessLSP(ProcessRecord app, ProcessRecord client,
+ boolean hasActivity, boolean hasService) {
mLruSeq++;
final long now = SystemClock.uptimeMillis();
- app.lastActivityTime = now;
+ final ProcessServiceRecord psr = app.mServices;
+ app.setLastActivityTime(now);
// First a quick reject: if the app is already at the position we will
// put it, then there is nothing to do.
@@ -3451,14 +3556,14 @@
if (hasActivity) {
final int N = mLruProcesses.size();
nextIndex = mLruProcessServiceStart;
- if (!app.hasActivitiesOrRecentTasks() && !app.treatLikeActivity
+ if (!app.hasActivitiesOrRecentTasks() && !psr.isTreatedLikeActivity()
&& mLruProcessActivityStart < (N - 1)) {
// Process doesn't have activities, but has clients with
// activities... move it up, but below the app that is binding to it.
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Adding to second-top of LRU activity list: " + app
- + " group=" + app.connectionGroup
- + " importance=" + app.connectionImportance);
+ + " group=" + psr.getConnectionGroup()
+ + " importance=" + psr.getConnectionImportance());
int pos = N - 1;
while (pos > mLruProcessActivityStart) {
final ProcessRecord posproc = mLruProcesses.get(pos);
@@ -3478,7 +3583,7 @@
endIndex = mLruProcessActivityStart;
}
nextActivityIndex = endIndex;
- updateClientActivitiesOrdering(app, pos, mLruProcessActivityStart, endIndex);
+ updateClientActivitiesOrderingLSP(app, pos, mLruProcessActivityStart, endIndex);
} else {
// Process has activities, put it at the very tipsy-top.
if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app);
@@ -3515,46 +3620,48 @@
mLruProcessActivityStart++;
mLruProcessServiceStart++;
if (index > 1) {
- updateClientActivitiesOrdering(app, mLruProcessServiceStart - 1, 0, index - 1);
+ updateClientActivitiesOrderingLSP(app, mLruProcessServiceStart - 1, 0, index - 1);
}
}
- app.lruSeq = mLruSeq;
+ app.setLruSeq(mLruSeq);
// If the app is currently using a content provider or service,
// bump those processes as well.
- for (int j = app.connections.size() - 1; j >= 0; j--) {
- ConnectionRecord cr = app.connections.valueAt(j);
+ for (int j = psr.numberOfConnections() - 1; j >= 0; j--) {
+ ConnectionRecord cr = psr.getConnectionAt(j);
if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
&& cr.binding.service.app != null
- && cr.binding.service.app.lruSeq != mLruSeq
+ && cr.binding.service.app.getLruSeq() != mLruSeq
&& (cr.flags & Context.BIND_REDUCTION_FLAGS) == 0
&& !cr.binding.service.app.isPersistent()) {
- if (cr.binding.service.app.hasClientActivities()) {
+ if (cr.binding.service.app.mServices.hasClientActivities()) {
if (nextActivityIndex >= 0) {
- nextActivityIndex = updateLruProcessInternalLocked(cr.binding.service.app,
+ nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app,
now,
nextActivityIndex, mLruSeq,
"service connection", cr, app);
}
} else {
- nextIndex = updateLruProcessInternalLocked(cr.binding.service.app,
+ nextIndex = updateLruProcessInternalLSP(cr.binding.service.app,
now,
nextIndex, mLruSeq,
"service connection", cr, app);
}
}
}
- for (int j = app.conProviders.size() - 1; j >= 0; j--) {
- ContentProviderRecord cpr = app.conProviders.get(j).provider;
- if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.isPersistent()) {
- nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, mLruSeq,
+ final ProcessProviderRecord ppr = app.mProviders;
+ for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) {
+ ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider;
+ if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) {
+ nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq,
"provider reference", cpr, app);
}
}
}
- final ProcessRecord getLRURecordForAppLocked(IApplicationThread thread) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ProcessRecord getLRURecordForAppLOSP(IApplicationThread thread) {
if (thread == null) {
return null;
}
@@ -3562,18 +3669,20 @@
// Find the application record.
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord rec = mLruProcesses.get(i);
- if (rec.thread != null && rec.thread.asBinder() == threadBinder) {
+ final IApplicationThread t = rec.getThread();
+ if (t != null && t.asBinder() == threadBinder) {
return rec;
}
}
return null;
}
- boolean haveBackgroundProcessLocked() {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean haveBackgroundProcessLOSP() {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord rec = mLruProcesses.get(i);
- if (rec.thread != null
- && rec.setProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+ if (rec.getThread() != null
+ && rec.mState.getSetProcState() >= PROCESS_STATE_CACHED_ACTIVITY) {
return true;
}
}
@@ -3593,11 +3702,11 @@
return imp;
}
- @GuardedBy("mService")
- void fillInProcMemInfoLocked(ProcessRecord app,
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void fillInProcMemInfoLOSP(ProcessRecord app,
ActivityManager.RunningAppProcessInfo outInfo,
int clientTargetSdk) {
- outInfo.pid = app.pid;
+ outInfo.pid = app.getPid();
outInfo.uid = app.info.uid;
if (app.getWindowProcessController().isHeavyWeightProcess()) {
outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE;
@@ -3609,49 +3718,53 @@
outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES;
}
outInfo.lastTrimLevel = app.mProfile.getTrimMemoryLevel();
- int adj = app.curAdj;
- int procState = app.getCurProcState();
+ final ProcessStateRecord state = app.mState;
+ int adj = state.getCurAdj();
+ int procState = state.getCurProcState();
outInfo.importance = procStateToImportance(procState, adj, outInfo,
clientTargetSdk);
- outInfo.importanceReasonCode = app.adjTypeCode;
- outInfo.processState = app.getCurProcState();
+ outInfo.importanceReasonCode = state.getAdjTypeCode();
+ outInfo.processState = procState;
outInfo.isFocused = (app == mService.getTopApp());
- outInfo.lastActivityTime = app.lastActivityTime;
+ outInfo.lastActivityTime = app.getLastActivityTime();
}
- @GuardedBy("mService")
- List<ActivityManager.RunningAppProcessInfo> getRunningAppProcessesLocked(boolean allUsers,
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ List<ActivityManager.RunningAppProcessInfo> getRunningAppProcessesLOSP(boolean allUsers,
int userId, boolean allUids, int callingUid, int clientTargetSdk) {
// Lazy instantiation of list
List<ActivityManager.RunningAppProcessInfo> runList = null;
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
+ final ProcessStateRecord state = app.mState;
+ final ProcessErrorStateRecord errState = app.mErrorState;
if ((!allUsers && app.userId != userId)
|| (!allUids && app.uid != callingUid)) {
continue;
}
- if ((app.thread != null) && (!app.isCrashing() && !app.isNotResponding())) {
+ if ((app.getThread() != null)
+ && (!errState.isCrashing() && !errState.isNotResponding())) {
// Generate process state info for running application
ActivityManager.RunningAppProcessInfo currApp =
new ActivityManager.RunningAppProcessInfo(app.processName,
- app.pid, app.getPackageList());
- fillInProcMemInfoLocked(app, currApp, clientTargetSdk);
- if (app.adjSource instanceof ProcessRecord) {
- currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid;
+ app.getPid(), app.getPackageList());
+ fillInProcMemInfoLOSP(app, currApp, clientTargetSdk);
+ if (state.getAdjSource() instanceof ProcessRecord) {
+ currApp.importanceReasonPid = ((ProcessRecord) state.getAdjSource()).getPid();
currApp.importanceReasonImportance =
ActivityManager.RunningAppProcessInfo.procStateToImportance(
- app.adjSourceProcState);
- } else if (app.adjSource instanceof ActivityServiceConnectionsHolder) {
+ state.getAdjSourceProcState());
+ } else if (state.getAdjSource() instanceof ActivityServiceConnectionsHolder) {
final ActivityServiceConnectionsHolder r =
- (ActivityServiceConnectionsHolder) app.adjSource;
+ (ActivityServiceConnectionsHolder) state.getAdjSource();
final int pid = r.getActivityPid();
if (pid != -1) {
currApp.importanceReasonPid = pid;
}
}
- if (app.adjTarget instanceof ComponentName) {
- currApp.importanceReasonComponent = (ComponentName)app.adjTarget;
+ if (state.getAdjTarget() instanceof ComponentName) {
+ currApp.importanceReasonComponent = (ComponentName) state.getAdjTarget();
}
//Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance
// + " lru=" + currApp.lru);
@@ -3664,11 +3777,110 @@
return runList;
}
- @GuardedBy("mService")
- int getLruSizeLocked() {
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ int getLruSizeLOSP() {
return mLruProcesses.size();
}
+ /**
+ * Return the reference to the LRU list, call this function for read-only access
+ */
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ ArrayList<ProcessRecord> getLruProcessesLOSP() {
+ return mLruProcesses;
+ }
+
+ /**
+ * Return the reference to the LRU list, call this function for read/write access
+ */
+ @GuardedBy({"mService", "mProfileLock"})
+ ArrayList<ProcessRecord> getLruProcessesLSP() {
+ return mLruProcesses;
+ }
+
+ /**
+ * For test only
+ */
+ @VisibleForTesting
+ @GuardedBy({"mService", "mProfileLock"})
+ void setLruProcessServiceStartLSP(int pos) {
+ mLruProcessServiceStart = pos;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ int getLruProcessServiceStartLOSP() {
+ return mLruProcessServiceStart;
+ }
+
+ /**
+ * Iterate the whole LRU list, invoke the given {@code callback} with each of the ProcessRecord
+ * in that list.
+ *
+ * @param iterateForward If {@code true}, iterate the LRU list from the least recent used
+ * to most recent used ProcessRecord.
+ * @param callback The callback interface to accept the current ProcessRecord.
+ */
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ void forEachLruProcessesLOSP(boolean iterateForward,
+ @NonNull Consumer<ProcessRecord> callback) {
+ if (iterateForward) {
+ for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+ callback.accept(mLruProcesses.get(i));
+ }
+ } else {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+ callback.accept(mLruProcesses.get(i));
+ }
+ }
+ }
+
+ /**
+ * Search in the LRU list, invoke the given {@code callback} with each of the ProcessRecord
+ * in that list; if the callback returns a non-null object, halt the search, return that
+ * object as the return value of this search function.
+ *
+ * @param iterateForward If {@code true}, iterate the LRU list from the least recent used
+ * to most recent used ProcessRecord.
+ * @param callback The callback interface to accept the current ProcessRecord; if it returns
+ * a non-null object, the search will be halted and this object will be used
+ * as the return value of this search function.
+ */
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ <R> R searchEachLruProcessesLOSP(boolean iterateForward,
+ @NonNull Function<ProcessRecord, R> callback) {
+ if (iterateForward) {
+ for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+ R r;
+ if ((r = callback.apply(mLruProcesses.get(i))) != null) {
+ return r;
+ }
+ }
+ } else {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+ R r;
+ if ((r = callback.apply(mLruProcesses.get(i))) != null) {
+ return r;
+ }
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ boolean isInLruListLOSP(ProcessRecord app) {
+ return mLruProcesses.contains(app);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ int getLruSeqLOSP() {
+ return mLruSeq;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ MyProcessMap getProcessNamesLOSP() {
+ return mProcessNames;
+ }
+
@GuardedBy("mService")
void dumpLruListHeaderLocked(PrintWriter pw) {
pw.print(" Process LRU list (sorted by oom_adj, "); pw.print(mLruProcesses.size());
@@ -3679,7 +3891,8 @@
pw.println("):");
}
- void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc, String prefix) {
+ @GuardedBy("mService")
+ private void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc, String prefix) {
pw.print(prefix);
pw.print('#');
if (index < 10) {
@@ -3687,15 +3900,16 @@
}
pw.print(index);
pw.print(": ");
- pw.print(makeOomAdjString(proc.setAdj, false));
+ pw.print(makeOomAdjString(proc.mState.getSetAdj(), false));
pw.print(' ');
- pw.print(makeProcStateString(proc.getCurProcState()));
+ pw.print(makeProcStateString(proc.mState.getCurProcState()));
pw.print(' ');
- ActivityManager.printCapabilitiesSummary(pw, proc.curCapability);
+ ActivityManager.printCapabilitiesSummary(pw, proc.mState.getCurCapability());
pw.print(' ');
pw.print(proc.toShortString());
- if (proc.hasActivitiesOrRecentTasks() || proc.hasClientActivities()
- || proc.treatLikeActivity) {
+ final ProcessServiceRecord psr = proc.mServices;
+ if (proc.hasActivitiesOrRecentTasks() || psr.hasClientActivities()
+ || psr.isTreatedLikeActivity()) {
pw.print(" act:");
boolean printed = false;
if (proc.hasActivities()) {
@@ -3709,14 +3923,14 @@
pw.print("recents");
printed = true;
}
- if (proc.hasClientActivities()) {
+ if (psr.hasClientActivities()) {
if (printed) {
pw.print("|");
}
pw.print("client");
printed = true;
}
- if (proc.treatLikeActivity) {
+ if (psr.isTreatedLikeActivity()) {
if (printed) {
pw.print("|");
}
@@ -3726,6 +3940,7 @@
pw.println();
}
+ @GuardedBy("mService")
boolean dumpLruLocked(PrintWriter pw, String dumpPackage, String prefix) {
final int lruSize = mLruProcesses.size();
final String innerPrefix;
@@ -3792,8 +4007,8 @@
return true;
}
- @GuardedBy("mService")
- void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ @GuardedBy({"mService", "mProcLock"})
+ void dumpProcessesLSP(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
boolean needSep = false;
int numPers = 0;
@@ -3867,7 +4082,7 @@
needSep = true;
}
- if (getLruSizeLocked() > 0) {
+ if (getLruSizeLOSP() > 0) {
if (needSep) {
pw.println();
}
@@ -3877,12 +4092,12 @@
needSep = true;
}
- mService.dumpOtherProcessesInfoLocked(fd, pw, dumpAll, dumpPackage, dumpAppId, numPers,
+ mService.dumpOtherProcessesInfoLSP(fd, pw, dumpAll, dumpPackage, dumpAppId, numPers,
needSep);
}
- @GuardedBy("this")
- void writeProcessesToProtoLocked(ProtoOutputStream proto, String dumpPackage) {
+ @GuardedBy({"mService", "mProcLock"})
+ void writeProcessesToProtoLSP(ProtoOutputStream proto, String dumpPackage) {
int numPers = 0;
final int numOfNames = mProcessNames.getMap().size();
@@ -3916,9 +4131,9 @@
mActiveUids.dumpProto(proto, dumpPackage, dumpAppId,
ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
- if (getLruSizeLocked() > 0) {
+ if (getLruSizeLOSP() > 0) {
long lruToken = proto.start(ActivityManagerServiceDumpProcessesProto.LRU_PROCS);
- int total = getLruSizeLocked();
+ int total = getLruSizeLOSP();
proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.SIZE, total);
proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.NON_ACT_AT,
total - mLruProcessActivityStart);
@@ -3930,7 +4145,7 @@
proto.end(lruToken);
}
- mService.writeOtherProcessesInfoToProtoLocked(proto, dumpPackage, dumpAppId, numPers);
+ mService.writeOtherProcessesInfoToProtoLSP(proto, dumpPackage, dumpAppId, numPers);
}
private static ArrayList<Pair<ProcessRecord, Integer>> sortProcessOomList(
@@ -3950,14 +4165,18 @@
@Override
public int compare(Pair<ProcessRecord, Integer> object1,
Pair<ProcessRecord, Integer> object2) {
- if (object1.first.setAdj != object2.first.setAdj) {
- return object1.first.setAdj > object2.first.setAdj ? -1 : 1;
+ final int adj = object2.first.mState.getSetAdj() - object1.first.mState.getSetAdj();
+ if (adj != 0) {
+ return adj;
}
- if (object1.first.setProcState != object2.first.setProcState) {
- return object1.first.setProcState > object2.first.setProcState ? -1 : 1;
+ final int procState = object2.first.mState.getSetProcState()
+ - object1.first.mState.getSetProcState();
+ if (procState != 0) {
+ return procState;
}
- if (object1.second.intValue() != object2.second.intValue()) {
- return object1.second.intValue() > object2.second.intValue() ? -1 : 1;
+ final int val = object2.second - object1.second;
+ if (val != 0) {
+ return val;
}
return 0;
}
@@ -3977,13 +4196,15 @@
for (int i = list.size() - 1; i >= 0; i--) {
ProcessRecord r = list.get(i).first;
+ final ProcessStateRecord state = r.mState;
+ final ProcessServiceRecord psr = r.mServices;
long token = proto.start(fieldId);
- String oomAdj = makeOomAdjString(r.setAdj, true);
+ String oomAdj = makeOomAdjString(state.getSetAdj(), true);
proto.write(ProcessOomProto.PERSISTENT, r.isPersistent());
proto.write(ProcessOomProto.NUM, (origList.size() - 1) - list.get(i).second);
proto.write(ProcessOomProto.OOM_ADJ, oomAdj);
int schedGroup = ProcessOomProto.SCHED_GROUP_UNKNOWN;
- switch (r.setSchedGroup) {
+ switch (state.getSetSchedGroup()) {
case SCHED_GROUP_BACKGROUND:
schedGroup = ProcessOomProto.SCHED_GROUP_BACKGROUND;
break;
@@ -4000,52 +4221,52 @@
if (schedGroup != ProcessOomProto.SCHED_GROUP_UNKNOWN) {
proto.write(ProcessOomProto.SCHED_GROUP, schedGroup);
}
- if (r.hasForegroundActivities()) {
+ if (state.hasForegroundActivities()) {
proto.write(ProcessOomProto.ACTIVITIES, true);
- } else if (r.hasForegroundServices()) {
+ } else if (psr.hasForegroundServices()) {
proto.write(ProcessOomProto.SERVICES, true);
}
proto.write(ProcessOomProto.STATE,
- makeProcStateProtoEnum(r.getCurProcState()));
+ makeProcStateProtoEnum(state.getCurProcState()));
proto.write(ProcessOomProto.TRIM_MEMORY_LEVEL, r.mProfile.getTrimMemoryLevel());
r.dumpDebug(proto, ProcessOomProto.PROC);
- proto.write(ProcessOomProto.ADJ_TYPE, r.adjType);
- if (r.adjSource != null || r.adjTarget != null) {
- if (r.adjTarget instanceof ComponentName) {
- ComponentName cn = (ComponentName) r.adjTarget;
+ proto.write(ProcessOomProto.ADJ_TYPE, state.getAdjType());
+ if (state.getAdjSource() != null || state.getAdjTarget() != null) {
+ if (state.getAdjTarget() instanceof ComponentName) {
+ ComponentName cn = (ComponentName) state.getAdjTarget();
cn.dumpDebug(proto, ProcessOomProto.ADJ_TARGET_COMPONENT_NAME);
- } else if (r.adjTarget != null) {
- proto.write(ProcessOomProto.ADJ_TARGET_OBJECT, r.adjTarget.toString());
+ } else if (state.getAdjTarget() != null) {
+ proto.write(ProcessOomProto.ADJ_TARGET_OBJECT, state.getAdjTarget().toString());
}
- if (r.adjSource instanceof ProcessRecord) {
- ProcessRecord p = (ProcessRecord) r.adjSource;
+ if (state.getAdjSource() instanceof ProcessRecord) {
+ ProcessRecord p = (ProcessRecord) state.getAdjSource();
p.dumpDebug(proto, ProcessOomProto.ADJ_SOURCE_PROC);
- } else if (r.adjSource != null) {
- proto.write(ProcessOomProto.ADJ_SOURCE_OBJECT, r.adjSource.toString());
+ } else if (state.getAdjSource() != null) {
+ proto.write(ProcessOomProto.ADJ_SOURCE_OBJECT, state.getAdjSource().toString());
}
}
if (inclDetails) {
long detailToken = proto.start(ProcessOomProto.DETAIL);
- proto.write(ProcessOomProto.Detail.MAX_ADJ, r.maxAdj);
- proto.write(ProcessOomProto.Detail.CUR_RAW_ADJ, r.getCurRawAdj());
- proto.write(ProcessOomProto.Detail.SET_RAW_ADJ, r.setRawAdj);
- proto.write(ProcessOomProto.Detail.CUR_ADJ, r.curAdj);
- proto.write(ProcessOomProto.Detail.SET_ADJ, r.setAdj);
+ proto.write(ProcessOomProto.Detail.MAX_ADJ, state.getMaxAdj());
+ proto.write(ProcessOomProto.Detail.CUR_RAW_ADJ, state.getCurRawAdj());
+ proto.write(ProcessOomProto.Detail.SET_RAW_ADJ, state.getSetRawAdj());
+ proto.write(ProcessOomProto.Detail.CUR_ADJ, state.getCurAdj());
+ proto.write(ProcessOomProto.Detail.SET_ADJ, state.getSetAdj());
proto.write(ProcessOomProto.Detail.CURRENT_STATE,
- makeProcStateProtoEnum(r.getCurProcState()));
+ makeProcStateProtoEnum(state.getCurProcState()));
proto.write(ProcessOomProto.Detail.SET_STATE,
- makeProcStateProtoEnum(r.setProcState));
+ makeProcStateProtoEnum(state.getSetProcState()));
proto.write(ProcessOomProto.Detail.LAST_PSS, DebugUtils.sizeValueToString(
r.mProfile.getLastPss() * 1024, new StringBuilder()));
proto.write(ProcessOomProto.Detail.LAST_SWAP_PSS, DebugUtils.sizeValueToString(
r.mProfile.getLastSwapPss() * 1024, new StringBuilder()));
proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString(
r.mProfile.getLastCachedPss() * 1024, new StringBuilder()));
- proto.write(ProcessOomProto.Detail.CACHED, r.isCached());
- proto.write(ProcessOomProto.Detail.EMPTY, r.empty);
- proto.write(ProcessOomProto.Detail.HAS_ABOVE_CLIENT, r.hasAboveClient);
+ proto.write(ProcessOomProto.Detail.CACHED, state.isCached());
+ proto.write(ProcessOomProto.Detail.EMPTY, state.isEmpty());
+ proto.write(ProcessOomProto.Detail.HAS_ABOVE_CLIENT, psr.hasAboveClient());
- if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
+ if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
long lastCpuTime = r.mProfile.mLastCpuTime.get();
if (lastCpuTime != 0) {
long uptimeSince = curUptime - service.mLastPowerCheckUptime;
@@ -4079,9 +4300,11 @@
for (int i = list.size() - 1; i >= 0; i--) {
ProcessRecord r = list.get(i).first;
- String oomAdj = makeOomAdjString(r.setAdj, false);
+ final ProcessStateRecord state = r.mState;
+ final ProcessServiceRecord psr = r.mServices;
+ String oomAdj = makeOomAdjString(state.getSetAdj(), false);
char schedGroup;
- switch (r.setSchedGroup) {
+ switch (state.getSetSchedGroup()) {
case SCHED_GROUP_BACKGROUND:
schedGroup = 'b';
break;
@@ -4102,14 +4325,14 @@
break;
}
char foreground;
- if (r.hasForegroundActivities()) {
+ if (state.hasForegroundActivities()) {
foreground = 'A';
- } else if (r.hasForegroundServices()) {
+ } else if (psr.hasForegroundServices()) {
foreground = 'S';
} else {
foreground = ' ';
}
- String procState = makeProcStateString(r.getCurProcState());
+ String procState = makeProcStateString(state.getCurProcState());
pw.print(prefix);
pw.print(r.isPersistent() ? persistentLabel : normalLabel);
pw.print(" #");
@@ -4125,7 +4348,7 @@
pw.print('/');
pw.print(procState);
pw.print(' ');
- ActivityManager.printCapabilitiesSummary(pw, r.curCapability);
+ ActivityManager.printCapabilitiesSummary(pw, state.getCurCapability());
pw.print(' ');
pw.print(" t:");
if (r.mProfile.getTrimMemoryLevel() < 10) pw.print(' ');
@@ -4133,25 +4356,25 @@
pw.print(' ');
pw.print(r.toShortString());
pw.print(" (");
- pw.print(r.adjType);
+ pw.print(state.getAdjType());
pw.println(')');
- if (r.adjSource != null || r.adjTarget != null) {
+ if (state.getAdjSource() != null || state.getAdjTarget() != null) {
pw.print(prefix);
pw.print(" ");
- if (r.adjTarget instanceof ComponentName) {
- pw.print(((ComponentName) r.adjTarget).flattenToShortString());
- } else if (r.adjTarget != null) {
- pw.print(r.adjTarget.toString());
+ if (state.getAdjTarget() instanceof ComponentName) {
+ pw.print(((ComponentName) state.getAdjTarget()).flattenToShortString());
+ } else if (state.getAdjTarget() != null) {
+ pw.print(state.getAdjTarget().toString());
} else {
pw.print("{null}");
}
pw.print("<=");
- if (r.adjSource instanceof ProcessRecord) {
+ if (state.getAdjSource() instanceof ProcessRecord) {
pw.print("Proc{");
- pw.print(((ProcessRecord) r.adjSource).toShortString());
+ pw.print(((ProcessRecord) state.getAdjSource()).toShortString());
pw.println("}");
- } else if (r.adjSource != null) {
- pw.println(r.adjSource.toString());
+ } else if (state.getAdjSource() != null) {
+ pw.println(state.getAdjSource().toString());
} else {
pw.println("{null}");
}
@@ -4159,16 +4382,15 @@
if (inclDetails) {
pw.print(prefix);
pw.print(" ");
- pw.print("oom: max="); pw.print(r.maxAdj);
- pw.print(" curRaw="); pw.print(r.getCurRawAdj());
- pw.print(" setRaw="); pw.print(r.setRawAdj);
- pw.print(" cur="); pw.print(r.curAdj);
- pw.print(" set="); pw.println(r.setAdj);
+ pw.print("oom: max="); pw.print(state.getMaxAdj());
+ pw.print(" curRaw="); pw.print(state.getCurRawAdj());
+ pw.print(" setRaw="); pw.print(state.getSetRawAdj());
+ pw.print(" cur="); pw.print(state.getCurAdj());
+ pw.print(" set="); pw.println(state.getSetAdj());
pw.print(prefix);
pw.print(" ");
- pw.print("state: cur="); pw.print(
- makeProcStateString(r.getCurProcState()));
- pw.print(" set="); pw.print(makeProcStateString(r.setProcState));
+ pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState()));
+ pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState()));
pw.print(" lastPss=");
DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
pw.print(" lastSwapPss=");
@@ -4178,11 +4400,11 @@
pw.println();
pw.print(prefix);
pw.print(" ");
- pw.print("cached="); pw.print(r.isCached());
- pw.print(" empty="); pw.print(r.empty);
- pw.print(" hasAboveClient="); pw.println(r.hasAboveClient);
+ pw.print("cached="); pw.print(state.isCached());
+ pw.print(" empty="); pw.print(state.isEmpty());
+ pw.print(" hasAboveClient="); pw.println(psr.hasAboveClient());
- if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
+ if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
long lastCpuTime = r.mProfile.mLastCpuTime.get();
if (lastCpuTime != 0) {
long timeUsed = r.mProfile.mCurCpuTime.get() - lastCpuTime;
@@ -4218,9 +4440,10 @@
pw.println(")");
}
+ @GuardedBy("mService")
boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String[] args,
int opti, boolean dumpAll, String dumpPackage, boolean inclGc) {
- if (getLruSizeLocked() > 0) {
+ if (getLruSizeLOSP() > 0) {
if (needSep) pw.println();
needSep = true;
pw.println(" OOM levels:");
@@ -4241,14 +4464,14 @@
printOomLevel(pw, "CACHED_APP_MAX_ADJ", CACHED_APP_MAX_ADJ);
if (needSep) pw.println();
- pw.print(" Process OOM control ("); pw.print(getLruSizeLocked());
+ pw.print(" Process OOM control ("); pw.print(getLruSizeLOSP());
pw.print(" total, non-act at ");
- pw.print(getLruSizeLocked() - mLruProcessActivityStart);
+ pw.print(getLruSizeLOSP() - mLruProcessActivityStart);
pw.print(", non-svc at ");
- pw.print(getLruSizeLocked() - mLruProcessServiceStart);
+ pw.print(getLruSizeLOSP() - mLruProcessServiceStart);
pw.println("):");
- dumpProcessOomList(pw, mService, mLruProcesses, " ", "Proc", "PERS", true,
- dumpPackage);
+ dumpProcessOomList(pw, mService, mLruProcesses,
+ " ", "Proc", "PERS", true, dumpPackage);
needSep = true;
}
@@ -4402,8 +4625,8 @@
mProcessObservers.finishBroadcast();
}
- @GuardedBy("mService")
- ArrayList<ProcessRecord> collectProcessesLocked(int start, boolean allPkgs, String[] args) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ArrayList<ProcessRecord> collectProcessesLOSP(int start, boolean allPkgs, String[] args) {
ArrayList<ProcessRecord> procs;
if (args != null && args.length > start
&& args[start].charAt(0) != '-') {
@@ -4415,7 +4638,7 @@
}
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord proc = mLruProcesses.get(i);
- if (proc.pid > 0 && proc.pid == pid) {
+ if (proc.getPid() > 0 && proc.getPid() == pid) {
procs.add(proc);
} else if (allPkgs && proc.getPkgList() != null
&& proc.getPkgList().containsKey(args[start])) {
@@ -4433,12 +4656,12 @@
return procs;
}
- @GuardedBy("mService")
- void updateApplicationInfoLocked(List<String> packagesToUpdate, int userId,
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void updateApplicationInfoLOSP(List<String> packagesToUpdate, int userId,
boolean updateFrameworkRes) {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord app = mLruProcesses.get(i);
- if (app.thread == null) {
+ if (app.getThread() == null) {
continue;
}
@@ -4447,19 +4670,19 @@
}
app.getPkgList().forEachPackage(packageName -> {
- if (updateFrameworkRes && packagesToUpdate.contains(packageName)) {
+ if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
try {
final ApplicationInfo ai = AppGlobals.getPackageManager()
.getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
if (ai != null) {
- app.thread.scheduleApplicationInfoChanged(ai);
+ app.getThread().scheduleApplicationInfoChanged(ai);
if (ai.packageName.equals(app.info.packageName)) {
app.info = ai;
}
}
} catch (RemoteException e) {
Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
- packageName, app));
+ packageName, app));
}
}
});
@@ -4471,14 +4694,15 @@
boolean foundProcess = false;
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
- if (r.thread != null && (userId == UserHandle.USER_ALL || r.userId == userId)) {
+ final IApplicationThread thread = r.getThread();
+ if (thread != null && (userId == UserHandle.USER_ALL || r.userId == userId)) {
try {
for (int index = packages.length - 1; index >= 0 && !foundProcess; index--) {
if (packages[index].equals(r.info.packageName)) {
foundProcess = true;
}
}
- r.thread.dispatchPackageBroadcast(cmd, packages);
+ thread.dispatchPackageBroadcast(cmd, packages);
} catch (RemoteException ex) {
}
}
@@ -4493,15 +4717,15 @@
}
/** Returns the uid's process state or PROCESS_STATE_NONEXISTENT if not running */
- @GuardedBy("mService")
- int getUidProcStateLocked(int uid) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getUidProcStateLOSP(int uid) {
UidRecord uidRec = mActiveUids.get(uid);
return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState();
}
/** Returns the UidRecord for the given uid, if it exists. */
- @GuardedBy("mService")
- UidRecord getUidRecordLocked(int uid) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ UidRecord getUidRecordLOSP(int uid) {
return mActiveUids.get(uid);
}
@@ -4518,10 +4742,10 @@
continue;
}
final UidRecord uidRec = mActiveUids.valueAt(i);
- if (!uidRec.idle) {
+ if (!uidRec.isIdle()) {
continue;
}
- mService.doStopUidLocked(uidRec.uid, uidRec);
+ mService.doStopUidLocked(uidRec.getUid(), uidRec);
}
}
@@ -4535,14 +4759,16 @@
* {@link #NETWORK_STATE_NO_CHANGE}.
*/
@VisibleForTesting
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
int getBlockStateForUid(UidRecord uidRec) {
// Denotes whether uid's process state is currently allowed network access.
final boolean isAllowed =
isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getCurProcState())
|| isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState());
// Denotes whether uid's process state was previously allowed network access.
- final boolean wasAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState)
- || isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
+ final boolean wasAllowed =
+ isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getSetProcState())
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState());
// When the uid is coming to foreground, AMS should inform the app thread that it should
// block for the network rules to get updated before launching an activity.
@@ -4563,8 +4789,8 @@
* {@link ProcessList#mProcStateSeqCounter} and notifies the app if it needs to block.
*/
@VisibleForTesting
- @GuardedBy("mService")
- void incrementProcStateSeqAndNotifyAppsLocked(ActiveUids activeUids) {
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void incrementProcStateSeqAndNotifyAppsLOSP(ActiveUids activeUids) {
if (mService.mWaitForNetworkTimeoutMs <= 0) {
return;
}
@@ -4573,14 +4799,14 @@
for (int i = activeUids.size() - 1; i >= 0; --i) {
final UidRecord uidRec = activeUids.valueAt(i);
// If the network is not restricted for uid, then nothing to do here.
- if (!mService.mInjector.isNetworkRestrictedForUid(uidRec.uid)) {
+ if (!mService.mInjector.isNetworkRestrictedForUid(uidRec.getUid())) {
continue;
}
- if (!UserHandle.isApp(uidRec.uid) || !uidRec.hasInternetPermission) {
+ if (!UserHandle.isApp(uidRec.getUid()) || !uidRec.hasInternetPermission) {
continue;
}
// If process state is not changed, then there's nothing to do.
- if (uidRec.setProcState == uidRec.getCurProcState()) {
+ if (uidRec.getSetProcState() == uidRec.getCurProcState()) {
continue;
}
final int blockState = getBlockStateForUid(uidRec);
@@ -4595,7 +4821,7 @@
if (blockingUids == null) {
blockingUids = new ArrayList<>();
}
- blockingUids.add(uidRec.uid);
+ blockingUids.add(uidRec.getUid());
} else {
if (DEBUG_NETWORK) {
Slog.d(TAG_NETWORK, "uid going to background, notifying all blocking"
@@ -4618,15 +4844,16 @@
if (!blockingUids.contains(app.uid)) {
continue;
}
- if (!app.killedByAm && app.thread != null) {
- final UidRecord uidRec = getUidRecordLocked(app.uid);
+ final IApplicationThread thread = app.getThread();
+ if (!app.isKilledByAm() && thread != null) {
+ final UidRecord uidRec = getUidRecordLOSP(app.uid);
try {
if (DEBUG_NETWORK) {
Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: "
+ uidRec);
}
if (uidRec != null) {
- app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+ thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
}
} catch (RemoteException ignored) {
}
@@ -4698,7 +4925,7 @@
Slog.i(TAG, "note: " + app + " died, saving the exit info");
}
- Watchdog.getInstance().processDied(app.processName, app.pid);
+ Watchdog.getInstance().processDied(app.processName, app.getPid());
mAppExitInfoTracker.scheduleNoteProcessDied(app);
}
@@ -4820,10 +5047,10 @@
}
final Bundle bundle = new Bundle();
- bundle.putInt(EXTRA_PID, app.pid);
+ bundle.putInt(EXTRA_PID, app.getPid());
bundle.putInt(EXTRA_UID, app.uid);
// Since the pid could be reused, let's get the actual start time of each process
- bundle.putLong(EXTRA_TIMESTAMP, app.startTime);
+ bundle.putLong(EXTRA_TIMESTAMP, app.getStartTime());
bundle.putString(EXTRA_REASON, reason);
bundle.putInt(EXTRA_REQUESTER, requester);
List<Bundle> list = mWorkItems.get(app.uid);
@@ -4915,13 +5142,14 @@
app = mService.mPidsSelfLocked.get(pid);
}
- if (app == null || app.pid != pid || app.uid != uid || app.startTime != timestamp) {
+ if (app == null || app.getPid() != pid || app.uid != uid
+ || app.getStartTime() != timestamp) {
// This process record has been reused for another process, meaning the old process
// has been gone.
return true;
}
- if (app.getPkgList().forEachPackage(pkgName -> {
+ if (app.getPkgList().searchEachPackage(pkgName -> {
if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.contains(pkgName)) {
// One of the packages in this process is exempted
return Boolean.TRUE;
@@ -4932,12 +5160,12 @@
}
if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.contains(
- app.getReportedProcState())) {
+ app.mState.getReportedProcState())) {
// We need to reschedule it.
return false;
}
- app.kill(reason, ApplicationExitInfo.REASON_OTHER,
+ app.killLocked(reason, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_IMPERCEPTIBLE, true);
if (!app.isolated) {
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index cf309f4..e533cc3 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -26,6 +26,7 @@
import android.util.DebugUtils;
import android.util.TimeUtils;
+import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.app.procstats.ProcessStats;
@@ -141,13 +142,13 @@
/**
* Last selected memory trimming level.
*/
- @GuardedBy("mService")
+ @CompositeRWLock({"mService", "mProcLock"})
private int mTrimMemoryLevel;
/**
* Want to clean up resources from showing UI?
*/
- @GuardedBy("mService")
+ @GuardedBy("mProcLock")
private boolean mPendingUiClean;
/**
@@ -164,7 +165,7 @@
/**
* When we last told the app that memory is low.
*/
- @GuardedBy("mService")
+ @CompositeRWLock({"mService", "mProfilerLock"})
private long mLastLowMemory;
/**
@@ -193,9 +194,12 @@
@GuardedBy("mProfilerLock")
private long mLastStateTime;
+ private final ActivityManagerGlobalLock mProcLock;
+
ProcessProfileRecord(final ProcessRecord app) {
mApp = app;
mService = app.mService;
+ mProcLock = mService.mProcLock;
mProfilerLock = mService.mAppProfiler.mProfilerLock;
}
@@ -410,22 +414,22 @@
mPssStatType = pssStatType;
}
- @GuardedBy("mService")
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
int getTrimMemoryLevel() {
return mTrimMemoryLevel;
}
- @GuardedBy("mService")
+ @GuardedBy({"mService", "mProcLock"})
void setTrimMemoryLevel(int trimMemoryLevel) {
mTrimMemoryLevel = trimMemoryLevel;
}
- @GuardedBy("mService")
+ @GuardedBy("mProcLock")
boolean hasPendingUiClean() {
return mPendingUiClean;
}
- @GuardedBy("mService")
+ @GuardedBy("mProcLock")
void setPendingUiClean(boolean pendingUiClean) {
mPendingUiClean = pendingUiClean;
mApp.getWindowProcessController().setPendingUiClean(pendingUiClean);
@@ -449,12 +453,12 @@
mLastRequestedGc = lastRequestedGc;
}
- @GuardedBy("mService")
+ @GuardedBy(anyOf = {"mService", "mProfilerLock"})
long getLastLowMemory() {
return mLastLowMemory;
}
- @GuardedBy("mService")
+ @GuardedBy({"mService", "mProfilerLock"})
void setLastLowMemory(long lastLowMemory) {
mLastLowMemory = lastLowMemory;
}
@@ -593,11 +597,11 @@
}
@GuardedBy({"mService", "mProfilerLock"})
- void updateProcState(ProcessRecord app) {
- mSetProcState = app.getCurProcState();
- mSetAdj = app.curAdj;
- mCurRawAdj = app.getCurRawAdj();
- mLastStateTime = app.lastStateTime;
+ void updateProcState(ProcessStateRecord state) {
+ mSetProcState = state.getCurProcState();
+ mSetAdj = state.getCurAdj();
+ mCurRawAdj = state.getCurRawAdj();
+ mLastStateTime = state.getLastStateTime();
}
@GuardedBy("mService")
@@ -623,6 +627,8 @@
DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024);
pw.print(" lastRss=");
DebugUtils.printSizeValue(pw, mLastRss * 1024);
+ pw.println();
+ pw.print(prefix);
pw.print(" trimMemoryLevel=");
pw.println(mTrimMemoryLevel);
pw.println();
diff --git a/services/core/java/com/android/server/am/ProcessProviderRecord.java b/services/core/java/com/android/server/am/ProcessProviderRecord.java
new file mode 100644
index 0000000..751e8a82
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessProviderRecord.java
@@ -0,0 +1,175 @@
+/*
+ * 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.am;
+
+import android.util.ArrayMap;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * The state info of all content providers in the process.
+ */
+final class ProcessProviderRecord {
+ final ProcessRecord mApp;
+ private final ActivityManagerService mService;
+
+ /**
+ * The last time someone else was using a provider in this process.
+ */
+ private long mLastProviderTime;
+
+ /**
+ * class (String) -> ContentProviderRecord.
+ */
+ private final ArrayMap<String, ContentProviderRecord> mPubProviders = new ArrayMap<>();
+
+ /**
+ * All ContentProviderRecord process is using.
+ */
+ private final ArrayList<ContentProviderConnection> mConProviders = new ArrayList<>();
+
+ long getLastProviderTime() {
+ return mLastProviderTime;
+ }
+
+ void setLastProviderTime(long lastProviderTime) {
+ mLastProviderTime = lastProviderTime;
+ }
+
+ boolean hasProvider(String name) {
+ return mPubProviders.containsKey(name);
+ }
+
+ ContentProviderRecord getProvider(String name) {
+ return mPubProviders.get(name);
+ }
+
+ int numberOfProviders() {
+ return mPubProviders.size();
+ }
+
+ ContentProviderRecord getProviderAt(int index) {
+ return mPubProviders.valueAt(index);
+ }
+
+ void installProvider(String name, ContentProviderRecord provider) {
+ mPubProviders.put(name, provider);
+ }
+
+ void removeProvider(String name) {
+ mPubProviders.remove(name);
+ }
+
+ void ensureProviderCapacity(int capacity) {
+ mPubProviders.ensureCapacity(capacity);
+ }
+
+ int numberOfProviderConnections() {
+ return mConProviders.size();
+ }
+
+ ContentProviderConnection getProviderConnectionAt(int index) {
+ return mConProviders.get(index);
+ }
+
+ void addProviderConnection(ContentProviderConnection connection) {
+ mConProviders.add(connection);
+ }
+
+ boolean removeProviderConnection(ContentProviderConnection connection) {
+ return mConProviders.remove(connection);
+ }
+
+ ProcessProviderRecord(ProcessRecord app) {
+ mApp = app;
+ mService = app.mService;
+ }
+
+ /**
+ * @return Should the process restart or not.
+ */
+ @GuardedBy("mService")
+ boolean onCleanupApplicationRecordLocked(boolean allowRestart) {
+ boolean restart = false;
+ // Remove published content providers.
+ for (int i = mPubProviders.size() - 1; i >= 0; i--) {
+ final ContentProviderRecord cpr = mPubProviders.valueAt(i);
+ if (cpr.proc != mApp) {
+ // If the hosting process record isn't really us, bail out
+ continue;
+ }
+ final boolean alwaysRemove = mApp.mErrorState.isBad() || !allowRestart;
+ final boolean inLaunching = mService.mCpHelper
+ .removeDyingProviderLocked(mApp, cpr, alwaysRemove);
+ if (!alwaysRemove && inLaunching && cpr.hasConnectionOrHandle()) {
+ // We left the provider in the launching list, need to
+ // restart it.
+ restart = true;
+ }
+
+ cpr.provider = null;
+ cpr.setProcess(null);
+ }
+ mPubProviders.clear();
+
+ // Take care of any launching providers waiting for this process.
+ if (mService.mCpHelper.cleanupAppInLaunchingProvidersLocked(mApp, false)) {
+ mService.mProcessList.noteProcessDiedLocked(mApp);
+ restart = true;
+ }
+
+ // Unregister from connected content providers.
+ if (!mConProviders.isEmpty()) {
+ for (int i = mConProviders.size() - 1; i >= 0; i--) {
+ final ContentProviderConnection conn = mConProviders.get(i);
+ conn.provider.connections.remove(conn);
+ mService.stopAssociationLocked(mApp.uid, mApp.processName, conn.provider.uid,
+ conn.provider.appInfo.longVersionCode, conn.provider.name,
+ conn.provider.info.processName);
+ }
+ mConProviders.clear();
+ }
+
+ return restart;
+ }
+
+ void dump(PrintWriter pw, String prefix, long nowUptime) {
+ if (mLastProviderTime > 0) {
+ pw.print(prefix); pw.print("lastProviderTime=");
+ TimeUtils.formatDuration(mLastProviderTime, nowUptime, pw);
+ pw.println();
+ }
+ if (mPubProviders.size() > 0) {
+ pw.print(prefix); pw.println("Published Providers:");
+ for (int i = 0, size = mPubProviders.size(); i < size; i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(mPubProviders.keyAt(i));
+ pw.print(prefix); pw.print(" -> "); pw.println(mPubProviders.valueAt(i));
+ }
+ }
+ if (mConProviders.size() > 0) {
+ pw.print(prefix); pw.println("Connected Providers:");
+ for (int i = 0, size = mConProviders.size(); i < size; i++) {
+ pw.print(prefix); pw.print(" - ");
+ pw.println(mConProviders.get(i).toShortString());
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessReceiverRecord.java b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
new file mode 100644
index 0000000..8d3e9669
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
@@ -0,0 +1,106 @@
+/*
+ * 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.am;
+
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * The state info of all broadcast receivers in the process.
+ */
+final class ProcessReceiverRecord {
+ final ProcessRecord mApp;
+ private final ActivityManagerService mService;
+
+ /**
+ * mReceivers currently running in the app.
+ */
+ private final ArraySet<BroadcastRecord> mCurReceivers = new ArraySet<BroadcastRecord>();
+
+ /**
+ * All IIntentReceivers that are registered from this process.
+ */
+ private final ArraySet<ReceiverList> mReceivers = new ArraySet<>();
+
+ int numberOfCurReceivers() {
+ return mCurReceivers.size();
+ }
+
+ BroadcastRecord getCurReceiverAt(int index) {
+ return mCurReceivers.valueAt(index);
+ }
+
+ boolean hasCurReceiver(BroadcastRecord receiver) {
+ return mCurReceivers.contains(receiver);
+ }
+
+ void addCurReceiver(BroadcastRecord receiver) {
+ mCurReceivers.add(receiver);
+ }
+
+ void removeCurReceiver(BroadcastRecord receiver) {
+ mCurReceivers.remove(receiver);
+ }
+
+ int numberOfReceivers() {
+ return mReceivers.size();
+ }
+
+ ReceiverList getReceiverAt(int index) {
+ return mReceivers.valueAt(index);
+ }
+
+ void addReceiver(ReceiverList receiver) {
+ mReceivers.add(receiver);
+ }
+
+ void removeReceiver(ReceiverList receiver) {
+ mReceivers.remove(receiver);
+ }
+
+ ProcessReceiverRecord(ProcessRecord app) {
+ mApp = app;
+ mService = app.mService;
+ }
+
+ @GuardedBy("mService")
+ void onCleanupApplicationRecordLocked() {
+ // Unregister any mReceivers.
+ for (int i = mReceivers.size() - 1; i >= 0; i--) {
+ mService.removeReceiverLocked(mReceivers.valueAt(i));
+ }
+ mReceivers.clear();
+ }
+
+ void dump(PrintWriter pw, String prefix, long nowUptime) {
+ if (!mCurReceivers.isEmpty()) {
+ pw.print(prefix); pw.println("Current mReceivers:");
+ for (int i = 0, size = mCurReceivers.size(); i < size; i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(mCurReceivers.valueAt(i));
+ }
+ }
+ if (mReceivers.size() > 0) {
+ pw.print(prefix); pw.println("mReceivers:");
+ for (int i = 0, size = mReceivers.size(); i < size; i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(mReceivers.valueAt(i));
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a1adeaa..da8aeb5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,78 +16,50 @@
package com.android.server.am;
-import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
-import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
-import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Process.NFC_UID;
-import static android.os.Process.ROOT_UID;
-import static android.os.Process.SHELL_UID;
-import static android.os.Process.SYSTEM_UID;
-
-import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.MY_PID;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
-import android.app.Dialog;
import android.app.IApplicationThread;
-import android.content.ComponentName;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IncrementalStatesInfo;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.ProcessInfo;
-import android.content.pm.ServiceInfo;
import android.content.pm.VersionedPackage;
import android.content.res.CompatibilityInfo;
import android.os.Binder;
import android.os.IBinder;
-import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.Settings;
import android.server.ServerProtoEnums;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.EventLog;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.Zygote;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.MemoryPressureUtil;
import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
-import java.io.File;
import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
-import java.util.function.Consumer;
/**
* Full information about a particular process that
@@ -97,6 +69,12 @@
static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessRecord" : TAG_AM;
final ActivityManagerService mService; // where we came from
+ private final ActivityManagerGlobalLock mProcLock;
+
+ // =========================================================
+ // Basic info of the process, immutable or semi-immutable over
+ // the lifecycle of the process
+ // =========================================================
volatile ApplicationInfo info; // all about the first app in the process
final ProcessInfo processInfo; // if non-null, process-specific manifest info
final boolean isolated; // true if this is a special isolated process
@@ -106,243 +84,288 @@
final String processName; // name of the process
/**
- * List of packages running in the process
+ * Overall state of process's uid.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private UidRecord mUidRecord;
+
+ /**
+ * List of packages running in the process.
*/
private final PackageList mPkgList = new PackageList(this);
- UidRecord uidRecord; // overall state of process's uid.
- ArraySet<String> pkgDeps; // additional packages we have a dependency on
- IApplicationThread thread; // the actual proc... may be null only if
- // 'persistent' is true (in which case we
- // are in the process of launching the app)
- int pid; // The process of this application; 0 if none
- String procStatFile; // path to /proc/<pid>/stat
- int[] gids; // The gids this process was launched with
- private String mRequiredAbi;// The ABI this process was launched with
- String instructionSet; // The instruction set this process was launched with
- boolean starting; // True if the process is being started
- long lastActivityTime; // For managing the LRU list
- long lastStateTime; // Last time setProcState changed
- int maxAdj; // Maximum OOM adjustment for this process
- private int mCurRawAdj; // Current OOM unlimited adjustment for this process
- int setRawAdj; // Last set OOM unlimited adjustment for this process
- int curAdj; // Current OOM adjustment for this process
- int setAdj; // Last set OOM adjustment for this process
- int verifiedAdj; // The last adjustment that was verified as actually being set
- int curCapability; // Current capability flags of this process. For example,
- // PROCESS_CAPABILITY_FOREGROUND_LOCATION is one capability.
- int setCapability; // Last set capability flags.
- long lastCompactTime; // The last time that this process was compacted
- int reqCompactAction; // The most recent compaction action requested for this app.
- int lastCompactAction; // The most recent compaction action performed for this app.
- boolean frozen; // True when the process is frozen.
- boolean freezerOverride; // An override on the freeze state is in progress.
- long freezeUnfreezeTime; // Last time the app was (un)frozen, 0 for never
- boolean shouldNotFreeze; // True if a process has a WPRI binding from an unfrozen process
- private int mCurSchedGroup; // Currently desired scheduling class
- int setSchedGroup; // Last set to background scheduling class
- private int mCurProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
- private int mRepProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
- private int mCurRawProcState = PROCESS_STATE_NONEXISTENT; // Temp state during computation
- int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
- int savedPriority; // Previous priority value if we're switching to non-SCHED_OTHER
- int renderThreadTid; // TID for RenderThread
- ServiceRecord connectionService; // Service that applied current connectionGroup/Importance
- int connectionGroup; // Last group set by a connection
- int connectionImportance; // Last importance set by a connection
- boolean serviceb; // Process currently is on the service B list
- boolean serviceHighRam; // We are forcing to service B list due to its RAM use
- boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
- private boolean mHasClientActivities; // Are there any client services with activities?
- boolean hasStartedServices; // Are there any started services running in this process?
- private boolean mHasForegroundServices; // Running any services that are foreground?
- private int mFgServiceTypes; // Type of foreground service, if there is a foreground service.
- private int mRepFgServiceTypes; // Last reported foreground service types.
- private boolean mHasForegroundActivities; // Running any activities that are foreground?
- boolean repForegroundActivities; // Last reported foreground activities.
- boolean systemNoUi; // This is a system process, but not currently showing UI.
- boolean hasShownUi; // Has UI been shown in this process since it was started?
- private boolean mHasTopUi; // Is this process currently showing a non-activity UI that the user
- // is interacting with? E.g. The status bar when it is expanded, but
- // not when it is minimized. When true the
- // process will be set to use the ProcessList#SCHED_GROUP_TOP_APP
- // scheduling group to boost performance.
- private boolean mHasOverlayUi; // Is the process currently showing a non-activity UI that
- // overlays on-top of activity UIs on screen. E.g. display a window
- // of type
- // android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
- // When true the process will oom adj score will be set to
- // ProcessList#PERCEPTIBLE_APP_ADJ at minimum to reduce the chance
- // of the process getting killed.
- boolean runningRemoteAnimation; // Is the process currently running a RemoteAnimation? When true
- // the process will be set to use the
- // ProcessList#SCHED_GROUP_TOP_APP scheduling group to boost
- // performance, as well as oom adj score will be set to
- // ProcessList#VISIBLE_APP_ADJ at minimum to reduce the chance
- // of the process getting killed.
- boolean hasAboveClient; // Bound using BIND_ABOVE_CLIENT, so want to be lower
- boolean treatLikeActivity; // Bound using BIND_TREAT_LIKE_ACTIVITY
- boolean bad; // True if disabled in the bad process list
- boolean killedByAm; // True when proc has been killed by activity manager, not for RAM
- boolean killed; // True once we know the process has been killed
- boolean procStateChanged; // Keep track of whether we changed 'setAdj'.
- boolean reportedInteraction;// Whether we have told usage stats about it being an interaction
- boolean unlocked; // True when proc was started in user unlocked state
- private long mInteractionEventTime; // The time we sent the last interaction event
- private long mFgInteractionTime; // When we became foreground for interaction purposes
- String waitingToKill; // Process is waiting to be killed when in the bg, and reason
- Object forcingToImportant; // Token that is forcing this process to be important
- int adjSeq; // Sequence id for identifying oom_adj assignment cycles
- int completedAdjSeq; // Sequence id for identifying oom_adj assignment cycles
- boolean containsCycle; // Whether this app has encountered a cycle in the most recent update
- int lruSeq; // Sequence id for identifying LRU update cycles
- CompatibilityInfo compat; // last used compatibility mode
- IBinder.DeathRecipient deathRecipient; // Who is watching for the death.
- private ActiveInstrumentation mInstr; // Set to currently active instrumentation running in
- // process.
- private boolean mUsingWrapper; // Set to true when process was launched with a wrapper attached
- final ArraySet<BroadcastRecord> curReceivers = new ArraySet<BroadcastRecord>();// receivers currently running in the app
- private long mWhenUnimportant; // When (uptime) the process last became unimportant
- long lastProviderTime; // The last time someone else was using a provider in this process.
- long lastTopTime; // The last time the process was in the TOP state or greater.
- boolean empty; // Is this an empty background process?
- private boolean mCached; // Is this a cached process?
- String adjType; // Debugging: primary thing impacting oom_adj.
- int adjTypeCode; // Debugging: adj code to report to app.
- Object adjSource; // Debugging: option dependent object.
- int adjSourceProcState; // Debugging: proc state of adjSource's process.
- Object adjTarget; // Debugging: target component impacting oom_adj.
- Runnable crashHandler; // Optional local handler to be invoked in the process crash.
- boolean bindMountPending; // True if Android/obb and Android/data need to be bind mount .
+ /**
+ * Additional packages we have a dependency on.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private ArraySet<String> mPkgDeps;
- // Controller for error dialogs
- private final ErrorDialogController mDialogController = new ErrorDialogController();
- // Controller for driving the process state on the window manager side.
+ /**
+ * The process of this application; 0 if none.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ int mPid;
+
+ /**
+ * The gids this process was launched with.
+ */
+ @GuardedBy("mService")
+ private int[] mGids;
+
+ /**
+ * The ABI this process was launched with.
+ */
+ @GuardedBy("mService")
+ private String mRequiredAbi;
+
+ /**
+ * The instruction set this process was launched with.
+ */
+ @GuardedBy("mService")
+ private String mInstructionSet;
+
+ /**
+ * The actual proc... may be null only if 'persistent' is true
+ * (in which case we are in the process of launching the app).
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private IApplicationThread mThread;
+
+ /**
+ * Always keep this application running?
+ */
+ private volatile boolean mPersistent;
+
+ /**
+ * Caching of toShortString() result.
+ * <p>Note: No lock here, it doesn't matter in case of race condition</p>
+ */
+ private String mShortStringName;
+
+ /**
+ * Caching of toString() result.
+ * <p>Note: No lock here, it doesn't matter in case of race condition</p>
+ */
+ private String mStringName;
+
+ /**
+ * Process start is pending.
+ */
+ @GuardedBy("mService")
+ private boolean mPendingStart;
+
+ /**
+ * Seq no. Indicating the latest process start associated with this process record.
+ */
+ @GuardedBy("mService")
+ private long mStartSeq;
+
+ /**
+ * Params used in starting this process.
+ */
+ private volatile HostingRecord mHostingRecord;
+
+ /**
+ * Selinux info of this process.
+ */
+ private volatile String mSeInfo;
+
+ /**
+ * When the process is started.
+ */
+ private volatile long mStartTime;
+
+ /**
+ * This will be same as {@link #uid} usually except for some apps used during factory testing.
+ */
+ private volatile int mStartUid;
+
+ /**
+ * Indicates how the external storage was mounted for this process.
+ */
+ private volatile int mMountMode;
+
+ /**
+ * True if Android/obb and Android/data need to be bind mount.
+ */
+ private volatile boolean mBindMountPending;
+
+ /**
+ * True when proc was started in user unlocked state.
+ */
+ @GuardedBy("mProcLock")
+ private boolean mUnlocked;
+
+ /**
+ * TID for RenderThread.
+ */
+ @GuardedBy("mProcLock")
+ private int mRenderThreadTid;
+
+ /**
+ * Last used compatibility mode.
+ */
+ @GuardedBy("mService")
+ private CompatibilityInfo mCompat;
+
+ /**
+ * Set of disabled compat changes for the process (all others are enabled).
+ */
+ @GuardedBy("mService")
+ private long[] mDisabledCompatChanges;
+
+ /**
+ * Who is watching for the death.
+ */
+ @GuardedBy("mService")
+ private IBinder.DeathRecipient mDeathRecipient;
+
+ /**
+ * Set to currently active instrumentation running in process.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private ActiveInstrumentation mInstr;
+
+ /**
+ * True when proc has been killed by activity manager, not for RAM.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mKilledByAm;
+
+ /**
+ * True once we know the process has been killed.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mKilled;
+
+ /**
+ * The timestamp in uptime when this process was killed.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mKillTime;
+
+ /**
+ * Process is waiting to be killed when in the bg, and reason.
+ */
+ @GuardedBy("mService")
+ private String mWaitingToKill;
+
+ /**
+ * Whether this process should be killed and removed from process list.
+ * It is set when the package is force-stopped or the process has crashed too many times.
+ */
+ private volatile boolean mRemoved;
+
+ /**
+ * Was app launched for debugging?
+ */
+ @GuardedBy("mService")
+ private boolean mDebugging;
+
+ /**
+ * Has process show wait for debugger dialog?
+ */
+ @GuardedBy("mProcLock")
+ private boolean mWaitedForDebugger;
+
+ /**
+ * For managing the LRU list.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mLastActivityTime;
+
+ /**
+ * Set to true when process was launched with a wrapper attached.
+ */
+ @GuardedBy("mService")
+ private boolean mUsingWrapper;
+
+ /**
+ * Sequence id for identifying LRU update cycles.
+ */
+ @GuardedBy("mService")
+ private int mLruSeq;
+
+ /**
+ * Class to run on start if this is a special isolated process.
+ */
+ @GuardedBy("mService")
+ private String mIsolatedEntryPoint;
+
+ /**
+ * Arguments to pass to isolatedEntryPoint's main().
+ */
+ @GuardedBy("mService")
+ private String[] mIsolatedEntryPointArgs;
+
+ /**
+ * Process is currently hosting a backup agent for backup or restore.
+ */
+ @GuardedBy("mService")
+ private boolean mInFullBackup;
+
+ /**
+ * Controller for driving the process state on the window manager side.
+ */
private final WindowProcessController mWindowProcessController;
- // all ServiceRecord running in this process
- private final ArraySet<ServiceRecord> mServices = new ArraySet<>();
- // services that are currently executing code (need to remain foreground).
- final ArraySet<ServiceRecord> executingServices = new ArraySet<>();
- // All ConnectionRecord this process holds
- final ArraySet<ConnectionRecord> connections = new ArraySet<>();
- // all IIntentReceivers that are registered from this process.
- final ArraySet<ReceiverList> receivers = new ArraySet<>();
- // class (String) -> ContentProviderRecord
- final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>();
- // All ContentProviderRecord process is using
- final ArrayList<ContentProviderConnection> conProviders = new ArrayList<>();
- // a set of UIDs of all bound clients
- private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
-
- String isolatedEntryPoint; // Class to run on start if this is a special isolated process.
- String[] isolatedEntryPointArgs; // Arguments to pass to isolatedEntryPoint's main().
-
- boolean execServicesFg; // do we need to be executing services in the foreground?
- private boolean mPersistent;// always keep this application running?
- private boolean mCrashing; // are we in the process of crashing?
- boolean forceCrashReport; // suppress normal auto-dismiss of crash dialog & report UI?
- private boolean mNotResponding; // does the app have a not responding dialog?
- volatile boolean removed; // Whether this process should be killed and removed from process
- // list. It is set when the package is force-stopped or the process
- // has crashed too many times.
- private boolean mDebugging; // was app launched for debugging?
- boolean waitedForDebugger; // has process show wait for debugger dialog?
-
- String shortStringName; // caching of toShortString() result.
- String stringName; // caching of toString() result.
- boolean pendingStart; // Process start is pending.
- long startSeq; // Seq no. indicating the latest process start associated with
- // this process record.
- int mountMode; // Indicates how the external storage was mounted for this process.
-
- // These reports are generated & stored when an app gets into an error condition.
- // They will be "null" when all is OK.
- ActivityManager.ProcessErrorStateInfo crashingReport;
- ActivityManager.ProcessErrorStateInfo notRespondingReport;
-
- // Who will be notified of the error. This is usually an activity in the
- // app that installed the package.
- ComponentName errorReportReceiver;
-
- // Process is currently hosting a backup agent for backup or restore
- public boolean inFullBackup;
- // App is allowed to manage allowlists such as temporary Power Save mode allowlist.
- boolean mAllowlistManager;
-
- // Params used in starting this process.
- HostingRecord hostingRecord;
- String seInfo;
- long startTime;
- // This will be same as {@link #uid} usually except for some apps used during factory testing.
- int startUid;
- // set of disabled compat changes for the process (all others are enabled)
- long[] mDisabledCompatChanges;
-
- // The precede instance of the process, which would exist when the previous process is killed
- // but not fully dead yet; in this case, the new instance of the process should be held until
- // this precede instance is fully dead.
- volatile ProcessRecord mPrecedence;
- // The succeeding instance of the process, which is going to be started after this process
- // is killed successfully.
- volatile ProcessRecord mSuccessor;
-
- // Cached task info for OomAdjuster
- private static final int VALUE_INVALID = -1;
- private static final int VALUE_FALSE = 0;
- private static final int VALUE_TRUE = 1;
- private int mCachedHasActivities = VALUE_INVALID;
- private int mCachedIsHeavyWeight = VALUE_INVALID;
- private int mCachedHasVisibleActivities = VALUE_INVALID;
- private int mCachedIsHomeProcess = VALUE_INVALID;
- private int mCachedIsPreviousProcess = VALUE_INVALID;
- private int mCachedHasRecentTasks = VALUE_INVALID;
- private int mCachedIsReceivingBroadcast = VALUE_INVALID;
- int mCachedAdj = ProcessList.INVALID_ADJ;
- boolean mCachedForegroundActivities = false;
- int mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-
- // Approximates the usage count of the app, used for cache re-ranking by CacheOomRanker.
- //
- // Counts the number of times the process is re-added to the cache (i.e. setCached(false);
- // setCached(true)). This over counts, as setCached is sometimes reset while remaining in the
- // cache. However, this happens uniformly across processes, so ranking is not affected.
- private int mCacheOomRankerUseCount;
-
- boolean mReachable; // Whether or not this process is reachable from given process
-
- long mKillTime; // The timestamp in uptime when this process was killed.
-
- // If the proc state is PROCESS_STATE_BOUND_FOREGROUND_SERVICE or above, it can start FGS.
- // It must obtain the proc state from a persistent/top process or FGS, not transitive.
- int mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
-
- private final ArraySet<Binder> mBackgroundFgsStartTokens = new ArraySet<>();
-
- // The list of permissions that can start FGS from background.
- private static String[] ALLOW_BG_START_FGS_PERMISSIONS =
- {START_ACTIVITIES_FROM_BACKGROUND, START_FOREGROUND_SERVICES_FROM_BACKGROUND,
- SYSTEM_ALERT_WINDOW};
- // Does the process has permission to start FGS from background.
- boolean mAllowStartFgsByPermission;
- // Can this process start FGS from background?
- // If this process has the ability to start FGS from background, this ability can be passed to
- // another process through service binding.
- boolean mAllowStartFgs;
/**
* Profiling info of the process, such as PSS, cpu, etc.
*/
final ProcessProfileRecord mProfile;
+ /**
+ * All about the services in this process.
+ */
+ final ProcessServiceRecord mServices;
+
+ /**
+ * All about the providers in this process.
+ */
+ final ProcessProviderRecord mProviders;
+
+ /**
+ * All about the receivers in this process.
+ */
+ final ProcessReceiverRecord mReceivers;
+
+ /**
+ * All about the error state(crash, ANR) in this process.
+ */
+ final ProcessErrorStateRecord mErrorState;
+
+ /**
+ * All about the process state info (proc state, oom adj score) in this process.
+ */
+ final ProcessStateRecord mState;
+
+ /**
+ * All about the state info of the optimizer when the process is cached.
+ */
+ final ProcessCachedOptimizerRecord mOptRecord;
+
+ /**
+ * The preceding instance of the process, which would exist when the previous process is killed
+ * but not fully dead yet; in this case, the new instance of the process should be held until
+ * this preceding instance is fully dead.
+ */
+ volatile ProcessRecord mPredecessor;
+
+ /**
+ * The succeeding instance of the process, which is going to be started after this process
+ * is killed successfully.
+ */
+ volatile ProcessRecord mSuccessor;
+
void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
long startTime) {
- this.startUid = startUid;
- this.hostingRecord = hostingRecord;
- this.seInfo = seInfo;
- this.startTime = startTime;
+ this.mStartUid = startUid;
+ this.mHostingRecord = hostingRecord;
+ this.mSeInfo = seInfo;
+ this.mStartTime = startTime;
}
+ @GuardedBy({"mService", "mProcLock"})
void dump(PrintWriter pw, String prefix) {
final long nowUptime = SystemClock.uptimeMillis();
@@ -352,10 +375,10 @@
pw.print(" ISOLATED uid="); pw.print(uid);
}
pw.print(" gids={");
- if (gids != null) {
- for (int gi=0; gi<gids.length; gi++) {
+ if (mGids != null) {
+ for (int gi = 0; gi < mGids.length; gi++) {
if (gi != 0) pw.print(", ");
- pw.print(gids[gi]);
+ pw.print(mGids[gi]);
}
}
@@ -371,9 +394,12 @@
if (processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
pw.print(prefix); pw.println(" gwpAsanMode=" + processInfo.gwpAsanMode);
}
+ if (processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+ pw.print(prefix); pw.println(" memtagMode=" + processInfo.memtagMode);
+ }
}
pw.print(prefix); pw.print("mRequiredAbi="); pw.print(mRequiredAbi);
- pw.print(" instructionSet="); pw.println(instructionSet);
+ pw.print(" instructionSet="); pw.println(mInstructionSet);
if (info.className != null) {
pw.print(prefix); pw.print("class="); pw.println(info.className);
}
@@ -386,210 +412,62 @@
pw.print(" publicDir="); pw.print(info.publicSourceDir);
pw.print(" data="); pw.println(info.dataDir);
mPkgList.dump(pw, prefix);
- pw.println("}");
- if (pkgDeps != null) {
+ if (mPkgDeps != null) {
pw.print(prefix); pw.print("packageDependencies={");
- for (int i=0; i<pkgDeps.size(); i++) {
+ for (int i = 0; i < mPkgDeps.size(); i++) {
if (i > 0) pw.print(", ");
- pw.print(pkgDeps.valueAt(i));
+ pw.print(mPkgDeps.valueAt(i));
}
pw.println("}");
}
- pw.print(prefix); pw.print("compat="); pw.println(compat);
+ pw.print(prefix); pw.print("compat="); pw.println(mCompat);
if (mInstr != null) {
pw.print(prefix); pw.print("mInstr="); pw.println(mInstr);
}
- pw.print(prefix); pw.print("thread="); pw.println(thread);
- pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting=");
- pw.println(starting);
+ pw.print(prefix); pw.print("thread="); pw.println(mThread);
+ pw.print(prefix); pw.print("pid="); pw.print(mPid);
pw.print(prefix); pw.print("lastActivityTime=");
- TimeUtils.formatDuration(lastActivityTime, nowUptime, pw);
- pw.println();
- pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
- pw.print(" lruSeq="); pw.println(lruSeq);
- pw.print(prefix); pw.print("oom adj: max="); pw.print(maxAdj);
- pw.print(" curRaw="); pw.print(mCurRawAdj);
- pw.print(" setRaw="); pw.print(setRawAdj);
- pw.print(" cur="); pw.print(curAdj);
- pw.print(" set="); pw.println(setAdj);
- pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
- pw.print(" lastCompactAction="); pw.println(lastCompactAction);
- pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
- pw.print(" setSchedGroup="); pw.print(setSchedGroup);
- pw.print(" systemNoUi="); pw.print(systemNoUi);
- pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
- pw.print(" mRepProcState="); pw.print(mRepProcState);
- pw.print(" setProcState="); pw.print(setProcState);
- pw.print(" lastStateTime=");
- TimeUtils.formatDuration(lastStateTime, nowUptime, pw);
- pw.println();
- pw.print(prefix); pw.print("curCapability=");
- ActivityManager.printCapabilitiesFull(pw, curCapability);
- pw.print(" setCapability=");
- ActivityManager.printCapabilitiesFull(pw, setCapability);
- pw.println();
- pw.print(prefix); pw.print("allowStartFgsState=");
- pw.println(mAllowStartFgsState);
- if (mAllowStartFgs) {
- pw.print(prefix); pw.print("allowStartFgs="); pw.println(mAllowStartFgs);
- }
- if (hasShownUi || mProfile.hasPendingUiClean() || hasAboveClient || treatLikeActivity) {
- pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
- pw.print(" pendingUiClean="); pw.print(mProfile.hasPendingUiClean());
- pw.print(" hasAboveClient="); pw.print(hasAboveClient);
- pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
- }
- pw.print(prefix); pw.print("cached="); pw.print(mCached);
- pw.print(" empty="); pw.println(empty);
- if (serviceb) {
- pw.print(prefix); pw.print("serviceb="); pw.print(serviceb);
- pw.print(" serviceHighRam="); pw.println(serviceHighRam);
- }
- if (notCachedSinceIdle) {
- pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(notCachedSinceIdle);
- pw.print(" initialIdlePss="); pw.println(mProfile.getInitialIdlePss());
- }
- if (connectionService != null || connectionGroup != 0) {
- pw.print(prefix); pw.print("connectionGroup="); pw.print(connectionGroup);
- pw.print(" Importance="); pw.print(connectionImportance);
- pw.print(" Service="); pw.println(connectionService);
- }
- if (hasTopUi() || hasOverlayUi() || runningRemoteAnimation) {
- pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi());
- pw.print(" hasOverlayUi="); pw.print(hasOverlayUi());
- pw.print(" runningRemoteAnimation="); pw.println(runningRemoteAnimation);
- }
- if (mHasForegroundServices || forcingToImportant != null) {
- pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices);
- pw.print(" forcingToImportant="); pw.println(forcingToImportant);
- }
- if (reportedInteraction || mFgInteractionTime != 0) {
- pw.print(prefix); pw.print("reportedInteraction=");
- pw.print(reportedInteraction);
- if (mInteractionEventTime != 0) {
- pw.print(" time=");
- TimeUtils.formatDuration(mInteractionEventTime, SystemClock.elapsedRealtime(), pw);
- }
- if (mFgInteractionTime != 0) {
- pw.print(" fgInteractionTime=");
- TimeUtils.formatDuration(mFgInteractionTime, SystemClock.elapsedRealtime(), pw);
- }
- pw.println();
- }
- if (mPersistent || removed) {
+ TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
+ if (mPersistent || mRemoved) {
pw.print(prefix); pw.print("persistent="); pw.print(mPersistent);
- pw.print(" removed="); pw.println(removed);
+ pw.print(" removed="); pw.println(mRemoved);
}
- if (mHasClientActivities || mHasForegroundActivities || repForegroundActivities) {
- pw.print(prefix); pw.print("hasClientActivities="); pw.print(mHasClientActivities);
- pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities);
- pw.print(" (rep="); pw.print(repForegroundActivities); pw.println(")");
+ if (mDebugging) {
+ pw.print(prefix); pw.print("mDebugging="); pw.println(mDebugging);
}
- if (lastProviderTime > 0) {
- pw.print(prefix); pw.print("lastProviderTime=");
- TimeUtils.formatDuration(lastProviderTime, nowUptime, pw);
- pw.println();
+ if (mPendingStart) {
+ pw.print(prefix); pw.print("pendingStart="); pw.println(mPendingStart);
}
- if (lastTopTime > 0) {
- pw.print(prefix); pw.print("lastTopTime=");
- TimeUtils.formatDuration(lastTopTime, nowUptime, pw);
- pw.println();
- }
- if (hasStartedServices) {
- pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
- }
- if (pendingStart) {
- pw.print(prefix); pw.print("pendingStart="); pw.println(pendingStart);
- }
- pw.print(prefix); pw.print("startSeq="); pw.println(startSeq);
+ pw.print(prefix); pw.print("startSeq="); pw.println(mStartSeq);
pw.print(prefix); pw.print("mountMode="); pw.println(
- DebugUtils.valueToString(Zygote.class, "MOUNT_EXTERNAL_", mountMode));
- if (setProcState > ActivityManager.PROCESS_STATE_SERVICE) {
+ DebugUtils.valueToString(Zygote.class, "MOUNT_EXTERNAL_", mMountMode));
+ if (mKilled || mKilledByAm || mWaitingToKill != null) {
+ pw.print(prefix); pw.print("killed="); pw.print(mKilled);
+ pw.print(" killedByAm="); pw.print(mKilledByAm);
+ pw.print(" waitingToKill="); pw.println(mWaitingToKill);
+ }
+ if (mIsolatedEntryPoint != null || mIsolatedEntryPointArgs != null) {
+ pw.print(prefix); pw.print("isolatedEntryPoint="); pw.println(mIsolatedEntryPoint);
+ pw.print(prefix); pw.print("isolatedEntryPointArgs=");
+ pw.println(Arrays.toString(mIsolatedEntryPointArgs));
+ }
+ if (mState.getSetProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
mProfile.dumpCputime(pw, prefix);
- pw.print(" whenUnimportant=");
- TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw);
- pw.println();
}
mProfile.dumpPss(pw, prefix, nowUptime);
- if (killed || killedByAm || waitingToKill != null) {
- pw.print(prefix); pw.print("killed="); pw.print(killed);
- pw.print(" killedByAm="); pw.print(killedByAm);
- pw.print(" waitingToKill="); pw.println(waitingToKill);
- }
- if (mDebugging || mCrashing || mDialogController.hasCrashDialogs() || mNotResponding
- || mDialogController.hasAnrDialogs() || bad) {
- pw.print(prefix); pw.print("mDebugging="); pw.print(mDebugging);
- pw.print(" mCrashing=" + mCrashing);
- pw.print(" " + mDialogController.mCrashDialogs);
- pw.print(" mNotResponding=" + mNotResponding);
- pw.print(" " + mDialogController.mAnrDialogs);
- pw.print(" bad=" + bad);
-
- // mCrashing or mNotResponding is always set before errorReportReceiver
- if (errorReportReceiver != null) {
- pw.print(" errorReportReceiver=");
- pw.print(errorReportReceiver.flattenToShortString());
- }
- pw.println();
- }
- if (mAllowlistManager) {
- pw.print(prefix); pw.print("allowlistManager="); pw.println(mAllowlistManager);
- }
- if (isolatedEntryPoint != null || isolatedEntryPointArgs != null) {
- pw.print(prefix); pw.print("isolatedEntryPoint="); pw.println(isolatedEntryPoint);
- pw.print(prefix); pw.print("isolatedEntryPointArgs=");
- pw.println(Arrays.toString(isolatedEntryPointArgs));
- }
+ mState.dump(pw, prefix, nowUptime);
+ mErrorState.dump(pw, prefix, nowUptime);
+ mServices.dump(pw, prefix, nowUptime);
+ mProviders.dump(pw, prefix, nowUptime);
+ mReceivers.dump(pw, prefix, nowUptime);
+ mOptRecord.dump(pw, prefix, nowUptime);
mWindowProcessController.dump(pw, prefix);
- if (mServices.size() > 0) {
- pw.print(prefix); pw.println("Services:");
- for (int i = 0; i < mServices.size(); i++) {
- pw.print(prefix); pw.print(" - "); pw.println(mServices.valueAt(i));
- }
- }
- if (executingServices.size() > 0) {
- pw.print(prefix); pw.print("Executing Services (fg=");
- pw.print(execServicesFg); pw.println(")");
- for (int i=0; i<executingServices.size(); i++) {
- pw.print(prefix); pw.print(" - "); pw.println(executingServices.valueAt(i));
- }
- }
- if (connections.size() > 0) {
- pw.print(prefix); pw.println("Connections:");
- for (int i=0; i<connections.size(); i++) {
- pw.print(prefix); pw.print(" - "); pw.println(connections.valueAt(i));
- }
- }
- if (pubProviders.size() > 0) {
- pw.print(prefix); pw.println("Published Providers:");
- for (int i=0; i<pubProviders.size(); i++) {
- pw.print(prefix); pw.print(" - "); pw.println(pubProviders.keyAt(i));
- pw.print(prefix); pw.print(" -> "); pw.println(pubProviders.valueAt(i));
- }
- }
- if (conProviders.size() > 0) {
- pw.print(prefix); pw.println("Connected Providers:");
- for (int i=0; i<conProviders.size(); i++) {
- pw.print(prefix); pw.print(" - "); pw.println(conProviders.get(i).toShortString());
- }
- }
- if (!curReceivers.isEmpty()) {
- pw.print(prefix); pw.println("Current Receivers:");
- for (int i=0; i < curReceivers.size(); i++) {
- pw.print(prefix); pw.print(" - "); pw.println(curReceivers.valueAt(i));
- }
- }
- if (receivers.size() > 0) {
- pw.print(prefix); pw.println("Receivers:");
- for (int i=0; i<receivers.size(); i++) {
- pw.print(prefix); pw.print(" - "); pw.println(receivers.valueAt(i));
- }
- }
}
ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
int _uid) {
mService = _service;
+ mProcLock = _service.mProcLock;
info = _info;
ProcessInfo procInfo = null;
if (_service.mPackageManagerInt != null) {
@@ -598,9 +476,12 @@
if (processes != null) {
procInfo = processes.get(_processName);
if (procInfo != null && procInfo.deniedPermissions == null
- && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT) {
+ && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
+ && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
+ && procInfo.nativeHeapZeroInit == null) {
// If this process hasn't asked for permissions to be denied, or for a
- // non-default GwpAsan mode, then we don't care about it.
+ // non-default GwpAsan mode, or any other non-default setting, then we don't
+ // care about it.
procInfo = null;
}
}
@@ -612,118 +493,393 @@
uid = _uid;
userId = UserHandle.getUserId(_uid);
processName = _processName;
- maxAdj = ProcessList.UNKNOWN_ADJ;
- mCurRawAdj = setRawAdj = ProcessList.INVALID_ADJ;
- curAdj = setAdj = verifiedAdj = ProcessList.INVALID_ADJ;
mPersistent = false;
- removed = false;
+ mRemoved = false;
mProfile = new ProcessProfileRecord(this);
+ mServices = new ProcessServiceRecord(this);
+ mProviders = new ProcessProviderRecord(this);
+ mReceivers = new ProcessReceiverRecord(this);
+ mErrorState = new ProcessErrorStateRecord(this);
+ mState = new ProcessStateRecord(this);
+ mOptRecord = new ProcessCachedOptimizerRecord(this);
final long now = SystemClock.uptimeMillis();
- freezeUnfreezeTime = lastStateTime = now;
mProfile.init(now);
+ mOptRecord.init(now);
+ mState.init(now);
mWindowProcessController = new WindowProcessController(
mService.mActivityTaskManager, info, processName, uid, userId, this, this);
mPkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.longVersionCode));
- setAllowStartFgsByPermission();
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ UidRecord getUidRecord() {
+ return mUidRecord;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setUidRecord(UidRecord uidRecord) {
+ mUidRecord = uidRecord;
}
PackageList getPkgList() {
return mPkgList;
}
- public void setPid(int _pid) {
- pid = _pid;
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ArraySet<String> getPkgDeps() {
+ return mPkgDeps;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setPkgDeps(ArraySet<String> pkgDeps) {
+ mPkgDeps = pkgDeps;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getPid() {
+ return mPid;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setPid(int pid) {
+ mPid = pid;
mWindowProcessController.setPid(pid);
- procStatFile = null;
- shortStringName = null;
- stringName = null;
+ mShortStringName = null;
+ mStringName = null;
synchronized (mProfile.mProfilerLock) {
mProfile.setPid(pid);
}
}
- public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) {
- mProfile.onProcessActive(_thread, tracker);
- thread = _thread;
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ IApplicationThread getThread() {
+ return mThread;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
+ mProfile.onProcessActive(thread, tracker);
+ mThread = thread;
mWindowProcessController.setThread(thread);
}
+ @GuardedBy({"mService", "mProcLock"})
public void makeInactive(ProcessStatsService tracker) {
- thread = null;
+ mThread = null;
mWindowProcessController.setThread(null);
mProfile.onProcessInactive(tracker);
}
- /**
- * Records a service as running in the process. Note that this method does not actually start
- * the service, but records the service as started for bookkeeping.
- *
- * @return true if the service was added, false otherwise.
- */
- boolean startService(ServiceRecord record) {
- if (record == null) {
- return false;
+ @GuardedBy("mService")
+ int[] getGids() {
+ return mGids;
+ }
+
+ @GuardedBy("mService")
+ void setGids(int[] gids) {
+ mGids = gids;
+ }
+
+ @GuardedBy("mService")
+ String getRequiredAbi() {
+ return mRequiredAbi;
+ }
+
+ @GuardedBy("mService")
+ void setRequiredAbi(String requiredAbi) {
+ mRequiredAbi = requiredAbi;
+ mWindowProcessController.setRequiredAbi(requiredAbi);
+ }
+
+ @GuardedBy("mService")
+ String getInstructionSet() {
+ return mInstructionSet;
+ }
+
+ @GuardedBy("mService")
+ void setInstructionSet(String instructionSet) {
+ mInstructionSet = instructionSet;
+ }
+
+ void setPersistent(boolean persistent) {
+ mPersistent = persistent;
+ mWindowProcessController.setPersistent(persistent);
+ }
+
+ boolean isPersistent() {
+ return mPersistent;
+ }
+
+ @GuardedBy("mService")
+ boolean isPendingStart() {
+ return mPendingStart;
+ }
+
+ @GuardedBy("mService")
+ void setPendingStart(boolean pendingStart) {
+ mPendingStart = pendingStart;
+ }
+
+ @GuardedBy("mService")
+ long getStartSeq() {
+ return mStartSeq;
+ }
+
+ @GuardedBy("mService")
+ void setStartSeq(long startSeq) {
+ mStartSeq = startSeq;
+ }
+
+ HostingRecord getHostingRecord() {
+ return mHostingRecord;
+ }
+
+ void setHostingRecord(HostingRecord hostingRecord) {
+ mHostingRecord = hostingRecord;
+ }
+
+ String getSeInfo() {
+ return mSeInfo;
+ }
+
+ void setSeInfo(String seInfo) {
+ mSeInfo = seInfo;
+ }
+
+ long getStartTime() {
+ return mStartTime;
+ }
+
+ void setStartTime(long startTime) {
+ mStartTime = startTime;
+ }
+
+ int getStartUid() {
+ return mStartUid;
+ }
+
+ void setStartUid(int startUid) {
+ mStartUid = startUid;
+ }
+
+ int getMountMode() {
+ return mMountMode;
+ }
+
+ void setMountMode(int mountMode) {
+ mMountMode = mountMode;
+ }
+
+ boolean isBindMountPending() {
+ return mBindMountPending;
+ }
+
+ void setBindMountPending(boolean bindMountPending) {
+ mBindMountPending = bindMountPending;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean isUnlocked() {
+ return mUnlocked;
+ }
+
+ @GuardedBy("mProcLock")
+ void setUnlocked(boolean unlocked) {
+ mUnlocked = unlocked;
+ }
+
+ @GuardedBy("mProcLock")
+ int getRenderThreadTid() {
+ return mRenderThreadTid;
+ }
+
+ @GuardedBy("mProcLock")
+ void setRenderThreadTid(int renderThreadTid) {
+ mRenderThreadTid = renderThreadTid;
+ }
+
+ @GuardedBy("mService")
+ CompatibilityInfo getCompat() {
+ return mCompat;
+ }
+
+ @GuardedBy("mService")
+ void setCompat(CompatibilityInfo compat) {
+ mCompat = compat;
+ }
+
+ @GuardedBy("mService")
+ long[] getDisabledCompatChanges() {
+ return mDisabledCompatChanges;
+ }
+
+ @GuardedBy("mService")
+ void setDisabledCompatChanges(long[] disabledCompatChanges) {
+ mDisabledCompatChanges = disabledCompatChanges;
+ }
+
+ @GuardedBy("mService")
+ void unlinkDeathRecipient() {
+ if (mDeathRecipient != null && mThread != null) {
+ mThread.asBinder().unlinkToDeath(mDeathRecipient, 0);
}
- boolean added = mServices.add(record);
- if (added && record.serviceInfo != null) {
- mWindowProcessController.onServiceStarted(record.serviceInfo);
- }
- return added;
+ mDeathRecipient = null;
}
- /**
- * Records a service as stopped. Note that like {@link #startService(ServiceRecord)} this method
- * does not actually stop the service, but records the service as stopped for bookkeeping.
- *
- * @return true if the service was removed, false otherwise.
- */
- boolean stopService(ServiceRecord record) {
- return mServices.remove(record);
+ @GuardedBy("mService")
+ void setDeathRecipient(IBinder.DeathRecipient deathRecipient) {
+ mDeathRecipient = deathRecipient;
}
- /**
- * The same as calling {@link #stopService(ServiceRecord)} on all current running services.
- */
- void stopAllServices() {
- mServices.clear();
+ @GuardedBy({"mService", "mProcLock"})
+ void setActiveInstrumentation(ActiveInstrumentation instr) {
+ mInstr = instr;
+ boolean isInstrumenting = instr != null;
+ mWindowProcessController.setInstrumenting(
+ isInstrumenting,
+ isInstrumenting ? instr.mSourceUid : -1,
+ isInstrumenting && instr.mHasBackgroundActivityStartsPermission);
}
- /**
- * Returns the number of services added with {@link #startService(ServiceRecord)} and not yet
- * removed by a call to {@link #stopService(ServiceRecord)} or {@link #stopAllServices()}.
- *
- * @see #startService(ServiceRecord)
- * @see #stopService(ServiceRecord)
- */
- int numberOfRunningServices() {
- return mServices.size();
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ ActiveInstrumentation getActiveInstrumentation() {
+ return mInstr;
}
- /**
- * Returns the service at the specified {@code index}.
- *
- * @see #numberOfRunningServices()
- */
- ServiceRecord getRunningServiceAt(int index) {
- return mServices.valueAt(index);
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isKilledByAm() {
+ return mKilledByAm;
}
- void setCached(boolean cached) {
- if (mCached != cached) {
- mCached = cached;
- if (cached) {
- ++mCacheOomRankerUseCount;
- }
- }
+ @GuardedBy({"mService", "mProcLock"})
+ void setKilledByAm(boolean killedByAm) {
+ mKilledByAm = killedByAm;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isKilled() {
+ return mKilled;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setKilled(boolean killed) {
+ mKilled = killed;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getKillTime() {
+ return mKillTime;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setKillTime(long killTime) {
+ mKillTime = killTime;
+ }
+
+ @GuardedBy("mService")
+ String getWaitingToKill() {
+ return mWaitingToKill;
+ }
+
+ @GuardedBy("mService")
+ void setWaitingToKill(String waitingToKill) {
+ mWaitingToKill = waitingToKill;
+ }
+
+ @Override
+ public boolean isRemoved() {
+ return mRemoved;
+ }
+
+ void setRemoved(boolean removed) {
+ mRemoved = removed;
+ }
+
+ @GuardedBy("mService")
+ boolean isDebugging() {
+ return mDebugging;
+ }
+
+ @GuardedBy("mService")
+ void setDebugging(boolean debugging) {
+ mDebugging = debugging;
+ mWindowProcessController.setDebugging(debugging);
+ }
+
+ @GuardedBy("mProcLock")
+ boolean hasWaitedForDebugger() {
+ return mWaitedForDebugger;
+ }
+
+ @GuardedBy("mProcLock")
+ void setWaitedForDebugger(boolean waitedForDebugger) {
+ mWaitedForDebugger = waitedForDebugger;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getLastActivityTime() {
+ return mLastActivityTime;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setLastActivityTime(long lastActivityTime) {
+ mLastActivityTime = lastActivityTime;
+ }
+
+ @GuardedBy("mService")
+ boolean isUsingWrapper() {
+ return mUsingWrapper;
+ }
+
+ @GuardedBy("mService")
+ void setUsingWrapper(boolean usingWrapper) {
+ mUsingWrapper = usingWrapper;
+ mWindowProcessController.setUsingWrapper(usingWrapper);
+ }
+
+ @GuardedBy("mService")
+ int getLruSeq() {
+ return mLruSeq;
+ }
+
+ @GuardedBy("mService")
+ void setLruSeq(int lruSeq) {
+ mLruSeq = lruSeq;
+ }
+
+ @GuardedBy("mService")
+ String getIsolatedEntryPoint() {
+ return mIsolatedEntryPoint;
+ }
+
+ @GuardedBy("mService")
+ void setIsolatedEntryPoint(String isolatedEntryPoint) {
+ mIsolatedEntryPoint = isolatedEntryPoint;
+ }
+
+ @GuardedBy("mService")
+ String[] getIsolatedEntryPointArgs() {
+ return mIsolatedEntryPointArgs;
+ }
+
+ @GuardedBy("mService")
+ void setIsolatedEntryPointArgs(String[] isolatedEntryPointArgs) {
+ mIsolatedEntryPointArgs = isolatedEntryPointArgs;
+ }
+
+ @GuardedBy("mService")
+ boolean isInFullBackup() {
+ return mInFullBackup;
+ }
+
+ @GuardedBy("mService")
+ void setInFullBackup(boolean inFullBackup) {
+ mInFullBackup = inFullBackup;
}
@Override
public boolean isCached() {
- return mCached;
- }
-
- int getCacheOomRankerUseCount() {
- return mCacheOomRankerUseCount;
+ return mState.isCached();
}
boolean hasActivities() {
@@ -738,6 +894,22 @@
return mWindowProcessController.hasRecentTasks();
}
+ @GuardedBy({"mService", "mProcLock"})
+ boolean onCleanupApplicationRecordLSP(ProcessStatsService processStats, boolean allowRestart) {
+ mErrorState.onCleanupApplicationRecordLSP();
+
+ resetPackageList(processStats);
+ unlinkDeathRecipient();
+ makeInactive(processStats);
+ setWaitingToKill(null);
+
+ mState.onCleanupApplicationRecordLSP();
+ mServices.onCleanupApplicationRecordLocked();
+ mReceivers.onCleanupApplicationRecordLocked();
+
+ return mProviders.onCleanupApplicationRecordLocked(allowRestart);
+ }
+
/**
* This method returns true if any of the activities within the process record are interesting
* to the user. See HistoryRecord.isInterestingToUserLocked()
@@ -747,74 +919,26 @@
return true;
}
- final int servicesSize = mServices.size();
- for (int i = 0; i < servicesSize; i++) {
- ServiceRecord r = mServices.valueAt(i);
- if (r.isForeground) {
- return true;
- }
- }
- return false;
+ return mServices.hasForegroundServices();
}
- public void unlinkDeathRecipient() {
- if (deathRecipient != null && thread != null) {
- thread.asBinder().unlinkToDeath(deathRecipient, 0);
- }
- deathRecipient = null;
- }
-
- void updateHasAboveClientLocked() {
- hasAboveClient = false;
- for (int i=connections.size()-1; i>=0; i--) {
- ConnectionRecord cr = connections.valueAt(i);
- if ((cr.flags&Context.BIND_ABOVE_CLIENT) != 0) {
- hasAboveClient = true;
- break;
- }
- }
- }
-
- int modifyRawOomAdj(int adj) {
- if (hasAboveClient) {
- // If this process has bound to any services with BIND_ABOVE_CLIENT,
- // then we need to drop its adjustment to be lower than the service's
- // in order to honor the request. We want to drop it by one adjustment
- // level... but there is special meaning applied to various levels so
- // we will skip some of them.
- if (adj < ProcessList.FOREGROUND_APP_ADJ) {
- // System process will not get dropped, ever
- } else if (adj < ProcessList.VISIBLE_APP_ADJ) {
- adj = ProcessList.VISIBLE_APP_ADJ;
- } else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) {
- adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- } else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
- adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
- } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
- adj = ProcessList.CACHED_APP_MIN_ADJ;
- } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
- adj++;
- }
- }
- return adj;
- }
-
- void scheduleCrash(String message) {
+ @GuardedBy("mService")
+ void scheduleCrashLocked(String message) {
// Checking killedbyAm should keep it from showing the crash dialog if the process
// was already dead for a good / normal reason.
- if (!killedByAm) {
- if (thread != null) {
- if (pid == Process.myPid()) {
+ if (!mKilledByAm) {
+ if (mThread != null) {
+ if (mPid == Process.myPid()) {
Slog.w(TAG, "scheduleCrash: trying to crash system process!");
return;
}
final long ident = Binder.clearCallingIdentity();
try {
- thread.scheduleCrash(message);
+ mThread.scheduleCrash(message);
} catch (RemoteException e) {
// If it's already dead our work is done. If it's wedged just kill it.
// We won't get the crash dialog or the error reporting.
- kill("scheduleCrash for '" + message + "' failed",
+ killLocked("scheduleCrash for '" + message + "' failed",
ApplicationExitInfo.REASON_CRASH, true);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -823,30 +947,36 @@
}
}
- void kill(String reason, @Reason int reasonCode, boolean noisy) {
- kill(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
+ @GuardedBy("mService")
+ void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
+ killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
}
- void kill(String reason, @Reason int reasonCode, @SubReason int subReason, boolean noisy) {
- if (!killedByAm) {
+ @GuardedBy("mService")
+ void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
+ boolean noisy) {
+ if (!mKilledByAm) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
if (mService != null && (noisy || info.uid == mService.mCurOomAdjUid)) {
mService.reportUidInfoMessageLocked(TAG,
- "Killing " + toShortString() + " (adj " + setAdj + "): " + reason,
- info.uid);
+ "Killing " + toShortString() + " (adj " + mState.getSetAdj()
+ + "): " + reason, info.uid);
}
- if (pid > 0) {
+ if (mPid > 0) {
mService.mProcessList.noteAppKill(this, reasonCode, subReason, reason);
- EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
- Process.killProcessQuiet(pid);
- ProcessList.killProcessGroup(uid, pid);
+ EventLog.writeEvent(EventLogTags.AM_KILL,
+ userId, mPid, processName, mState.getSetAdj(), reason);
+ Process.killProcessQuiet(mPid);
+ ProcessList.killProcessGroup(uid, mPid);
} else {
- pendingStart = false;
+ mPendingStart = false;
}
if (!mPersistent) {
- killed = true;
- killedByAm = true;
- mKillTime = SystemClock.uptimeMillis();
+ synchronized (mProcLock) {
+ mKilled = true;
+ mKilledByAm = true;
+ mKillTime = SystemClock.uptimeMillis();
+ }
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -859,7 +989,7 @@
public void dumpDebug(ProtoOutputStream proto, long fieldId, int lruIndex) {
long token = proto.start(fieldId);
- proto.write(ProcessRecordProto.PID, pid);
+ proto.write(ProcessRecordProto.PID, mPid);
proto.write(ProcessRecordProto.PROCESS_NAME, processName);
proto.write(ProcessRecordProto.UID, info.uid);
if (UserHandle.getAppId(info.uid) >= Process.FIRST_APPLICATION_UID) {
@@ -877,16 +1007,17 @@
}
public String toShortString() {
+ final String shortStringName = mShortStringName;
if (shortStringName != null) {
return shortStringName;
}
StringBuilder sb = new StringBuilder(128);
toShortString(sb);
- return shortStringName = sb.toString();
+ return mShortStringName = sb.toString();
}
void toShortString(StringBuilder sb) {
- sb.append(pid);
+ sb.append(mPid);
sb.append(':');
sb.append(processName);
sb.append('/');
@@ -911,6 +1042,7 @@
}
public String toString() {
+ final String stringName = mStringName;
if (stringName != null) {
return stringName;
}
@@ -920,33 +1052,7 @@
sb.append(' ');
toShortString(sb);
sb.append('}');
- return stringName = sb.toString();
- }
-
- public String makeAdjReason() {
- if (adjSource != null || adjTarget != null) {
- StringBuilder sb = new StringBuilder(128);
- sb.append(' ');
- if (adjTarget instanceof ComponentName) {
- sb.append(((ComponentName)adjTarget).flattenToShortString());
- } else if (adjTarget != null) {
- sb.append(adjTarget.toString());
- } else {
- sb.append("{null}");
- }
- sb.append("<=");
- if (adjSource instanceof ProcessRecord) {
- sb.append("Proc{");
- sb.append(((ProcessRecord)adjSource).toShortString());
- sb.append("}");
- } else if (adjSource != null) {
- sb.append(adjSource.toString());
- } else {
- sb.append("{null}");
- }
- return sb.toString();
- }
- return null;
+ return mStringName = sb.toString();
}
/*
@@ -976,29 +1082,6 @@
return false;
}
- public int getSetAdjWithServices() {
- if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
- if (hasStartedServices) {
- return ProcessList.SERVICE_B_ADJ;
- }
- }
- return setAdj;
- }
-
- public void forceProcessStateUpTo(int newState) {
- if (mRepProcState > newState) {
- mRepProcState = newState;
- setCurProcState(newState);
- setCurRawProcState(newState);
- getPkgList().forEachPackage((pkgName, holder) ->
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
- uid, processName, pkgName,
- ActivityManager.processStateAmToProto(mRepProcState),
- holder.appVersion)
- );
- }
- }
-
/*
* Delete all packages from list except the package indicated in info
*/
@@ -1054,194 +1137,6 @@
return mWindowProcessController;
}
- void setCurrentSchedulingGroup(int curSchedGroup) {
- mCurSchedGroup = curSchedGroup;
- mWindowProcessController.setCurrentSchedulingGroup(curSchedGroup);
- }
-
- int getCurrentSchedulingGroup() {
- return mCurSchedGroup;
- }
-
- void setCurProcState(int curProcState) {
- mCurProcState = curProcState;
- mWindowProcessController.setCurrentProcState(mCurProcState);
- }
-
- int getCurProcState() {
- return mCurProcState;
- }
-
- void setCurRawProcState(int curRawProcState) {
- mCurRawProcState = curRawProcState;
- }
-
- int getCurRawProcState() {
- return mCurRawProcState;
- }
-
- void setReportedProcState(int repProcState) {
- mRepProcState = repProcState;
- getPkgList().forEachPackage((pkgName, holder) ->
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
- uid, processName, pkgName,
- ActivityManager.processStateAmToProto(mRepProcState),
- holder.appVersion)
- );
- mWindowProcessController.setReportedProcState(repProcState);
- }
-
- int getReportedProcState() {
- return mRepProcState;
- }
-
- void setCrashing(boolean crashing) {
- mCrashing = crashing;
- mWindowProcessController.setCrashing(crashing);
- }
-
- boolean isCrashing() {
- return mCrashing;
- }
-
- void setNotResponding(boolean notResponding) {
- mNotResponding = notResponding;
- mWindowProcessController.setNotResponding(notResponding);
- }
-
- boolean isNotResponding() {
- return mNotResponding;
- }
-
- void setPersistent(boolean persistent) {
- mPersistent = persistent;
- mWindowProcessController.setPersistent(persistent);
- }
-
- boolean isPersistent() {
- return mPersistent;
- }
-
- public void setRequiredAbi(String requiredAbi) {
- mRequiredAbi = requiredAbi;
- mWindowProcessController.setRequiredAbi(requiredAbi);
- }
-
- String getRequiredAbi() {
- return mRequiredAbi;
- }
-
- void setHasForegroundServices(boolean hasForegroundServices, int fgServiceTypes) {
- mHasForegroundServices = hasForegroundServices;
- mFgServiceTypes = fgServiceTypes;
- mWindowProcessController.setHasForegroundServices(hasForegroundServices);
- }
-
- boolean hasForegroundServices() {
- return mHasForegroundServices;
- }
-
- boolean hasLocationForegroundServices() {
- return mHasForegroundServices
- && (mFgServiceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION) != 0;
- }
-
- boolean hasLocationCapability() {
- return (setCapability & ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0;
- }
-
- int getForegroundServiceTypes() {
- return mHasForegroundServices ? mFgServiceTypes : 0;
- }
-
- int getReportedForegroundServiceTypes() {
- return mRepFgServiceTypes;
- }
-
- void setReportedForegroundServiceTypes(int foregroundServiceTypes) {
- mRepFgServiceTypes = foregroundServiceTypes;
- }
-
- void setHasForegroundActivities(boolean hasForegroundActivities) {
- mHasForegroundActivities = hasForegroundActivities;
- }
-
- boolean hasForegroundActivities() {
- return mHasForegroundActivities;
- }
-
- void setHasClientActivities(boolean hasClientActivities) {
- mHasClientActivities = hasClientActivities;
- mWindowProcessController.setHasClientActivities(hasClientActivities);
- }
-
- boolean hasClientActivities() {
- return mHasClientActivities;
- }
-
- void setHasTopUi(boolean hasTopUi) {
- mHasTopUi = hasTopUi;
- mWindowProcessController.setHasTopUi(hasTopUi);
- }
-
- boolean hasTopUi() {
- return mHasTopUi;
- }
-
- void setHasOverlayUi(boolean hasOverlayUi) {
- mHasOverlayUi = hasOverlayUi;
- mWindowProcessController.setHasOverlayUi(hasOverlayUi);
- }
-
- boolean hasOverlayUi() {
- return mHasOverlayUi;
- }
-
- void setInteractionEventTime(long interactionEventTime) {
- mInteractionEventTime = interactionEventTime;
- mWindowProcessController.setInteractionEventTime(interactionEventTime);
- }
-
- long getInteractionEventTime() {
- return mInteractionEventTime;
- }
-
- void setFgInteractionTime(long fgInteractionTime) {
- mFgInteractionTime = fgInteractionTime;
- mWindowProcessController.setFgInteractionTime(fgInteractionTime);
- }
-
- long getFgInteractionTime() {
- return mFgInteractionTime;
- }
-
- void setWhenUnimportant(long whenUnimportant) {
- mWhenUnimportant = whenUnimportant;
- mWindowProcessController.setWhenUnimportant(whenUnimportant);
- }
-
- long getWhenUnimportant() {
- return mWhenUnimportant;
- }
-
- void setDebugging(boolean debugging) {
- mDebugging = debugging;
- mWindowProcessController.setDebugging(debugging);
- }
-
- boolean isDebugging() {
- return mDebugging;
- }
-
- void setUsingWrapper(boolean usingWrapper) {
- mUsingWrapper = usingWrapper;
- mWindowProcessController.setUsingWrapper(usingWrapper);
- }
-
- boolean isUsingWrapper() {
- return mUsingWrapper;
- }
-
/**
* Allows background activity starts using token {@param entity}. Optionally, you can provide
* {@param originatingToken} if you have one such originating token, this is useful for tracing
@@ -1259,75 +1154,6 @@
mWindowProcessController.removeAllowBackgroundActivityStartsToken(entity);
}
- void addBoundClientUid(int clientUid) {
- mBoundClientUids.add(clientUid);
- mWindowProcessController.setBoundClientUids(mBoundClientUids);
- }
-
- void updateBoundClientUids() {
- if (mServices.isEmpty()) {
- clearBoundClientUids();
- return;
- }
- // grab a set of clientUids of all connections of all services
- ArraySet<Integer> boundClientUids = new ArraySet<>();
- final int serviceCount = mServices.size();
- for (int j = 0; j < serviceCount; j++) {
- ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns =
- mServices.valueAt(j).getConnections();
- final int N = conns.size();
- for (int conni = 0; conni < N; conni++) {
- ArrayList<ConnectionRecord> c = conns.valueAt(conni);
- for (int i = 0; i < c.size(); i++) {
- boundClientUids.add(c.get(i).clientUid);
- }
- }
- }
- mBoundClientUids = boundClientUids;
- mWindowProcessController.setBoundClientUids(mBoundClientUids);
- }
-
- void addBoundClientUidsOfNewService(ServiceRecord sr) {
- if (sr == null) {
- return;
- }
- ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = sr.getConnections();
- for (int conni = conns.size() - 1; conni >= 0; conni--) {
- ArrayList<ConnectionRecord> c = conns.valueAt(conni);
- for (int i = 0; i < c.size(); i++) {
- mBoundClientUids.add(c.get(i).clientUid);
- }
- }
- mWindowProcessController.setBoundClientUids(mBoundClientUids);
- }
-
- void clearBoundClientUids() {
- mBoundClientUids.clear();
- mWindowProcessController.setBoundClientUids(mBoundClientUids);
- }
-
- void setActiveInstrumentation(ActiveInstrumentation instr) {
- mInstr = instr;
- boolean isInstrumenting = instr != null;
- mWindowProcessController.setInstrumenting(
- isInstrumenting,
- isInstrumenting ? instr.mSourceUid : -1,
- isInstrumenting && instr.mHasBackgroundActivityStartsPermission);
- }
-
- ActiveInstrumentation getActiveInstrumentation() {
- return mInstr;
- }
-
- void setCurRawAdj(int curRawAdj) {
- mCurRawAdj = curRawAdj;
- mWindowProcessController.setPerceptible(curRawAdj <= ProcessList.PERCEPTIBLE_APP_ADJ);
- }
-
- int getCurRawAdj() {
- return mCurRawAdj;
- }
-
@Override
public void clearProfilerIfNeeded() {
synchronized (mService.mAppProfiler.mProfilerLock) {
@@ -1338,13 +1164,13 @@
@Override
public void updateServiceConnectionActivities() {
synchronized (mService) {
- mService.mServices.updateServiceConnectionActivitiesLocked(this);
+ mService.mServices.updateServiceConnectionActivitiesLocked(mServices);
}
}
@Override
public void setPendingUiClean(boolean pendingUiClean) {
- synchronized (mService) {
+ synchronized (mProcLock) {
mProfile.setPendingUiClean(pendingUiClean);
}
}
@@ -1353,7 +1179,7 @@
public void setPendingUiCleanAndForceProcessStateUpTo(int newState) {
synchronized (mService) {
setPendingUiClean(true);
- forceProcessStateUpTo(newState);
+ mState.forceProcessStateUpTo(newState);
}
}
@@ -1362,40 +1188,35 @@
boolean updateOomAdj) {
synchronized (mService) {
if (updateServiceConnectionActivities) {
- mService.mServices.updateServiceConnectionActivitiesLocked(this);
+ mService.mServices.updateServiceConnectionActivitiesLocked(mServices);
}
- if (thread == null) {
+ if (mThread == null) {
// Only update lru and oom-adj if the process is alive. Because it may be called
// when cleaning up the last activity from handling process died, the dead process
// should not be added to lru list again.
return;
}
- mService.mProcessList.updateLruProcessLocked(this, activityChange, null /* client */);
+ mService.updateLruProcessLocked(this, activityChange, null /* client */);
if (updateOomAdj) {
mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
}
}
}
- @Override
- public boolean isRemoved() {
- return removed;
- }
-
/**
* Returns the total time (in milliseconds) spent executing in both user and system code.
* Safe to call without lock held.
*/
@Override
public long getCpuTime() {
- return mService.mAppProfiler.getCpuTimeForPid(pid);
+ return mService.mAppProfiler.getCpuTimeForPid(mPid);
}
@Override
public void onStartActivity(int topProcessState, boolean setProfileProc, String packageName,
long versionCode) {
synchronized (mService) {
- waitingToKill = null;
+ mWaitingToKill = null;
if (setProfileProc) {
synchronized (mService.mAppProfiler.mProfilerLock) {
mService.mAppProfiler.setProfileProcLPf(this);
@@ -1408,9 +1229,9 @@
// Update oom adj first, we don't want the additional states are involved in this round.
updateProcessInfo(false /* updateServiceConnectionActivities */,
true /* activityChange */, true /* updateOomAdj */);
- hasShownUi = true;
setPendingUiClean(true);
- forceProcessStateUpTo(topProcessState);
+ mState.setHasShownUi(true);
+ mState.forceProcessStateUpTo(topProcessState);
}
}
@@ -1423,20 +1244,12 @@
@Override
public void setRunningRemoteAnimation(boolean runningRemoteAnimation) {
- if (pid == Process.myPid()) {
+ if (mPid == Process.myPid()) {
Slog.wtf(TAG, "system can't run remote animation");
return;
}
synchronized (mService) {
- if (this.runningRemoteAnimation == runningRemoteAnimation) {
- return;
- }
- this.runningRemoteAnimation = runningRemoteAnimation;
- if (DEBUG_OOM_ADJ) {
- Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
- + " for pid=" + pid);
- }
- mService.updateOomAdjLocked(this, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ mState.setRunningRemoteAnimation(runningRemoteAnimation);
}
}
@@ -1445,7 +1258,7 @@
}
public int getProcessClassEnum() {
- if (pid == MY_PID) {
+ if (mPid == MY_PID) {
return ServerProtoEnums.SYSTEM_SERVER;
}
if (info == null) {
@@ -1455,687 +1268,9 @@
ServerProtoEnums.DATA_APP;
}
- /**
- * Unless configured otherwise, swallow ANRs in background processes & kill the process.
- * Non-private access is for tests only.
- */
- @VisibleForTesting
- boolean isSilentAnr() {
- return !getShowBackground() && !isInterestingForBackgroundTraces();
- }
-
/** Non-private access is for tests only. */
@VisibleForTesting
List<ProcessRecord> getLruProcessList() {
- return mService.mProcessList.mLruProcesses;
- }
-
- /** Non-private access is for tests only. */
- @VisibleForTesting
- boolean isMonitorCpuUsage() {
- return mService.mAppProfiler.MONITOR_CPU_USAGE;
- }
-
- void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
- String parentShortComponentName, WindowProcessController parentProcess,
- boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
- ArrayList<Integer> firstPids = new ArrayList<>(5);
- SparseArray<Boolean> lastPids = new SparseArray<>(20);
-
- mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr",
- ApplicationExitInfo.REASON_ANR, true));
-
- long anrTime = SystemClock.uptimeMillis();
- if (isMonitorCpuUsage()) {
- mService.updateCpuStatsNow();
- }
-
- final boolean isSilentAnr;
- synchronized (mService) {
- // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
- if (mService.mAtmInternal.isShuttingDown()) {
- Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
- return;
- } else if (isNotResponding()) {
- Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
- return;
- } else if (isCrashing()) {
- Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
- return;
- } else if (killedByAm) {
- Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
- return;
- } else if (killed) {
- Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
- return;
- }
-
- // In case we come through here for the same app before completing
- // this one, mark as anring now so we will bail out.
- setNotResponding(true);
-
- // Log the ANR to the event log.
- EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
- annotation);
-
- // Dump thread traces as quickly as we can, starting with "interesting" processes.
- firstPids.add(pid);
-
- // Don't dump other PIDs if it's a background ANR or is requested to only dump self.
- isSilentAnr = isSilentAnr();
- if (!isSilentAnr && !onlyDumpSelf) {
- int parentPid = pid;
- if (parentProcess != null && parentProcess.getPid() > 0) {
- parentPid = parentProcess.getPid();
- }
- if (parentPid != pid) firstPids.add(parentPid);
-
- if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
-
- for (int i = getLruProcessList().size() - 1; i >= 0; i--) {
- ProcessRecord r = getLruProcessList().get(i);
- if (r != null && r.thread != null) {
- int myPid = r.pid;
- if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) {
- if (r.isPersistent()) {
- firstPids.add(myPid);
- if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
- } else if (r.treatLikeActivity) {
- firstPids.add(myPid);
- if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
- } else {
- lastPids.put(myPid, Boolean.TRUE);
- if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
- }
- }
- }
- }
- }
- }
-
- // Check if package is still being loaded
- boolean isPackageLoading = false;
- final PackageManagerInternal packageManagerInternal =
- mService.getPackageManagerInternal();
- if (aInfo != null && aInfo.packageName != null) {
- IncrementalStatesInfo incrementalStatesInfo =
- packageManagerInternal.getIncrementalStatesInfo(
- aInfo.packageName, uid, userId);
- if (incrementalStatesInfo != null) {
- isPackageLoading = incrementalStatesInfo.isLoading();
- }
- }
-
- // Log the ANR to the main log.
- StringBuilder info = new StringBuilder();
- info.setLength(0);
- info.append("ANR in ").append(processName);
- if (activityShortComponentName != null) {
- info.append(" (").append(activityShortComponentName).append(")");
- }
- info.append("\n");
- info.append("PID: ").append(pid).append("\n");
- if (annotation != null) {
- info.append("Reason: ").append(annotation).append("\n");
- }
- if (parentShortComponentName != null
- && parentShortComponentName.equals(activityShortComponentName)) {
- info.append("Parent: ").append(parentShortComponentName).append("\n");
- }
-
- if (isPackageLoading) {
- // Report in the main log that the package is still loading
- final float loadingProgress = packageManagerInternal.getIncrementalStatesInfo(
- aInfo.packageName, uid, userId).getProgress();
- info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
- }
-
- StringBuilder report = new StringBuilder();
- report.append(MemoryPressureUtil.currentPsiState());
- ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
-
- // don't dump native PIDs for background ANRs unless it is the process of interest
- String[] nativeProcs = null;
- if (isSilentAnr || onlyDumpSelf) {
- for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
- if (NATIVE_STACKS_OF_INTEREST[i].equals(processName)) {
- nativeProcs = new String[] { processName };
- break;
- }
- }
- } else {
- nativeProcs = NATIVE_STACKS_OF_INTEREST;
- }
-
- int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
- ArrayList<Integer> nativePids = null;
-
- if (pids != null) {
- nativePids = new ArrayList<>(pids.length);
- for (int i : pids) {
- nativePids.add(i);
- }
- }
-
- // For background ANRs, don't pass the ProcessCpuTracker to
- // avoid spending 1/2 second collecting stats to rank lastPids.
- StringWriter tracesFileException = new StringWriter();
- // To hold the start and end offset to the ANR trace file respectively.
- final long[] offsets = new long[2];
- File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
- isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
- nativePids, tracesFileException, offsets);
-
- if (isMonitorCpuUsage()) {
- mService.updateCpuStatsNow();
- mService.mAppProfiler.printCurrentCpuState(report, anrTime);
- info.append(processCpuTracker.printCurrentLoad());
- info.append(report);
- }
- report.append(tracesFileException.getBuffer());
-
- info.append(processCpuTracker.printCurrentState(anrTime));
-
- Slog.e(TAG, info.toString());
- if (tracesFile == null) {
- // There is no trace file, so dump (only) the alleged culprit's threads to the log
- Process.sendSignal(pid, Process.SIGNAL_QUIT);
- } else if (offsets[1] > 0) {
- // We've dumped into the trace file successfully
- mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
- pid, uid, getPackageList(), tracesFile, offsets[0], offsets[1]);
- }
-
- FrameworkStatsLog.write(FrameworkStatsLog.ANR_OCCURRED, uid, processName,
- activityShortComponentName == null ? "unknown": activityShortComponentName,
- annotation,
- (this.info != null) ? (this.info.isInstantApp()
- ? FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__TRUE
- : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__FALSE)
- : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE,
- isInterestingToUserLocked()
- ? FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND
- : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
- getProcessClassEnum(),
- (this.info != null) ? this.info.packageName : "", isPackageLoading);
- final ProcessRecord parentPr = parentProcess != null
- ? (ProcessRecord) parentProcess.mOwner : null;
- mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
- parentShortComponentName, parentPr, annotation, report.toString(), tracesFile,
- null);
-
- if (mWindowProcessController.appNotResponding(info.toString(), () -> kill("anr",
- ApplicationExitInfo.REASON_ANR, true),
- () -> {
- synchronized (mService) {
- mService.mServices.scheduleServiceTimeoutLocked(this);
- }
- })) {
- return;
- }
-
- synchronized (mService) {
- // mBatteryStatsService can be null if the AMS is constructed with injector only. This
- // will only happen in tests.
- if (mService.mBatteryStatsService != null) {
- mService.mBatteryStatsService.noteProcessAnr(processName, uid);
- }
-
- if (isSilentAnr() && !isDebugging()) {
- kill("bg anr", ApplicationExitInfo.REASON_ANR, true);
- return;
- }
-
- // Set the app's notResponding state, and look up the errorReportReceiver
- makeAppNotRespondingLocked(activityShortComponentName,
- annotation != null ? "ANR " + annotation : "ANR", info.toString());
-
- // Notify package manager service to possibly update package state
- if (aInfo != null && aInfo.packageName != null) {
- packageManagerInternal.notifyPackageCrashOrAnr(aInfo.packageName);
- }
-
- // mUiHandler can be null if the AMS is constructed with injector only. This will only
- // happen in tests.
- if (mService.mUiHandler != null) {
- // Bring up the infamous App Not Responding dialog
- Message msg = Message.obtain();
- msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
- msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);
-
- mService.mUiHandler.sendMessage(msg);
- }
- }
- }
-
- private void makeAppNotRespondingLocked(String activity, String shortMsg, String longMsg) {
- setNotResponding(true);
- // mAppErrors can be null if the AMS is constructed with injector only. This will only
- // happen in tests.
- if (mService.mAppErrors != null) {
- notRespondingReport = mService.mAppErrors.generateProcessError(this,
- ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
- activity, shortMsg, longMsg, null);
- }
- startAppProblemLocked();
- getWindowProcessController().stopFreezingActivities();
- }
-
- void startAppProblemLocked() {
- // If this app is not running under the current user, then we can't give it a report button
- // because that would require launching the report UI under a different user.
- errorReportReceiver = null;
-
- for (int userId : mService.mUserController.getCurrentProfileIds()) {
- if (this.userId == userId) {
- errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
- mService.mContext, info.packageName, info.flags);
- }
- }
- mService.skipCurrentReceiverLocked(this);
- }
-
- private boolean isInterestingForBackgroundTraces() {
- // The system_server is always considered interesting.
- if (pid == MY_PID) {
- return true;
- }
-
- // A package is considered interesting if any of the following is true :
- //
- // - It's displaying an activity.
- // - It's the SystemUI.
- // - It has an overlay or a top UI visible.
- //
- // NOTE: The check whether a given ProcessRecord belongs to the systemui
- // process is a bit of a kludge, but the same pattern seems repeated at
- // several places in the system server.
- return isInterestingToUserLocked() ||
- (info != null && "com.android.systemui".equals(info.packageName))
- || (hasTopUi() || hasOverlayUi());
- }
-
- private boolean getShowBackground() {
- return Settings.Secure.getInt(mService.mContext.getContentResolver(),
- Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
- }
-
- void resetCachedInfo() {
- mCachedHasActivities = VALUE_INVALID;
- mCachedIsHeavyWeight = VALUE_INVALID;
- mCachedHasVisibleActivities = VALUE_INVALID;
- mCachedIsHomeProcess = VALUE_INVALID;
- mCachedIsPreviousProcess = VALUE_INVALID;
- mCachedHasRecentTasks = VALUE_INVALID;
- mCachedIsReceivingBroadcast = VALUE_INVALID;
- mCachedAdj = ProcessList.INVALID_ADJ;
- mCachedForegroundActivities = false;
- mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- }
-
- boolean getCachedHasActivities() {
- if (mCachedHasActivities == VALUE_INVALID) {
- mCachedHasActivities = getWindowProcessController().hasActivities() ? VALUE_TRUE
- : VALUE_FALSE;
- }
- return mCachedHasActivities == VALUE_TRUE;
- }
-
- boolean getCachedIsHeavyWeight() {
- if (mCachedIsHeavyWeight == VALUE_INVALID) {
- mCachedIsHeavyWeight = getWindowProcessController().isHeavyWeightProcess()
- ? VALUE_TRUE : VALUE_FALSE;
- }
- return mCachedIsHeavyWeight == VALUE_TRUE;
- }
-
- boolean getCachedHasVisibleActivities() {
- if (mCachedHasVisibleActivities == VALUE_INVALID) {
- mCachedHasVisibleActivities = getWindowProcessController().hasVisibleActivities()
- ? VALUE_TRUE : VALUE_FALSE;
- }
- return mCachedHasVisibleActivities == VALUE_TRUE;
- }
-
- boolean getCachedIsHomeProcess() {
- if (mCachedIsHomeProcess == VALUE_INVALID) {
- if (getWindowProcessController().isHomeProcess()) {
- mCachedIsHomeProcess = VALUE_TRUE;
- mService.mAppProfiler.mHasHomeProcess = true;
- } else {
- mCachedIsHomeProcess = VALUE_FALSE;
- }
- }
- return mCachedIsHomeProcess == VALUE_TRUE;
- }
-
- boolean getCachedIsPreviousProcess() {
- if (mCachedIsPreviousProcess == VALUE_INVALID) {
- if (getWindowProcessController().isPreviousProcess()) {
- mCachedIsPreviousProcess = VALUE_TRUE;
- mService.mAppProfiler.mHasPreviousProcess = true;
- } else {
- mCachedIsPreviousProcess = VALUE_FALSE;
- }
- }
- return mCachedIsPreviousProcess == VALUE_TRUE;
- }
-
- boolean getCachedHasRecentTasks() {
- if (mCachedHasRecentTasks == VALUE_INVALID) {
- mCachedHasRecentTasks = getWindowProcessController().hasRecentTasks()
- ? VALUE_TRUE : VALUE_FALSE;
- }
- return mCachedHasRecentTasks == VALUE_TRUE;
- }
-
- boolean getCachedIsReceivingBroadcast(ArraySet<BroadcastQueue> tmpQueue) {
- if (mCachedIsReceivingBroadcast == VALUE_INVALID) {
- tmpQueue.clear();
- mCachedIsReceivingBroadcast = mService.isReceivingBroadcastLocked(this, tmpQueue)
- ? VALUE_TRUE : VALUE_FALSE;
- if (mCachedIsReceivingBroadcast == VALUE_TRUE) {
- mCachedSchedGroup = tmpQueue.contains(mService.mFgBroadcastQueue)
- ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
- }
- }
- return mCachedIsReceivingBroadcast == VALUE_TRUE;
- }
-
- void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
- int adj, boolean foregroundActivities, int procState, int schedGroup, int appUid,
- int logUid, int processCurTop) {
- if (mCachedAdj != ProcessList.INVALID_ADJ) {
- return;
- }
- callback.initialize(this, adj, foregroundActivities, procState, schedGroup, appUid, logUid,
- processCurTop);
- final int minLayer = Math.min(ProcessList.VISIBLE_APP_LAYER_MAX,
- getWindowProcessController().computeOomAdjFromActivities(callback));
-
- mCachedAdj = callback.adj;
- mCachedForegroundActivities = callback.foregroundActivities;
- mCachedProcState = callback.procState;
- mCachedSchedGroup = callback.schedGroup;
-
- if (mCachedAdj == ProcessList.VISIBLE_APP_ADJ) {
- mCachedAdj += minLayer;
- }
- }
-
- public void addAllowBackgroundFgsStartsToken(Binder entity) {
- mBackgroundFgsStartTokens.add(entity);
- }
-
- public void removeAllowBackgroundFgsStartsToken(Binder entity) {
- mBackgroundFgsStartTokens.remove(entity);
- }
-
- public boolean areBackgroundFgsStartsAllowedByToken() {
- return !mBackgroundFgsStartTokens.isEmpty();
- }
-
- ErrorDialogController getDialogController() {
- return mDialogController;
- }
-
- void resetAllowStartFgs() {
- mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
- mAllowStartFgs = mAllowStartFgsByPermission;
- }
-
- void bumpAllowStartFgsState(int newProcState) {
- if (newProcState < mAllowStartFgsState) {
- mAllowStartFgsState = newProcState;
- }
- }
-
- void setAllowStartFgsByPermission() {
- boolean ret = false;
- if (!ret) {
- boolean isSystem = false;
- final int uid = UserHandle.getAppId(info.uid);
- switch (uid) {
- case ROOT_UID:
- case SYSTEM_UID:
- case NFC_UID:
- case SHELL_UID:
- isSystem = true;
- break;
- default:
- isSystem = false;
- break;
- }
-
- if (isSystem) {
- ret = true;
- }
- }
-
- if (!ret) {
- for (int i = 0; i < ALLOW_BG_START_FGS_PERMISSIONS.length; ++i) {
- if (ActivityManager.checkComponentPermission(ALLOW_BG_START_FGS_PERMISSIONS[i],
- info.uid, -1, true)
- == PERMISSION_GRANTED) {
- ret = true;
- break;
- }
- }
- }
- mAllowStartFgs = mAllowStartFgsByPermission = ret;
- }
-
- boolean isAllowedStartFgsState() {
- return mAllowStartFgsState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- }
-
- void setAllowStartFgs() {
- if (mAllowStartFgs) {
- return;
- }
- if (!mAllowStartFgs) {
- mAllowStartFgs = isAllowedStartFgsState();
- }
-
- if (!mAllowStartFgs) {
- // Is the calling UID a device owner app?
- if (mService.mInternal != null) {
- mAllowStartFgs = mService.mInternal.isDeviceOwner(info.uid);
- }
- }
-
- if (!mAllowStartFgs) {
- if (mService.mInternal != null) {
- mAllowStartFgs = mService.mInternal.isAssociatedCompanionApp(
- UserHandle.getUserId(info.uid), info.uid);
- }
- }
-
- if (!mAllowStartFgs) {
- // Is the calling UID a profile owner app?
- if (mService.mInternal != null) {
- mAllowStartFgs = mService.mInternal.isProfileOwner(info.uid);
- }
- }
-
- if (!mAllowStartFgs) {
- // uid is on DeviceIdleController's user/system allowlist
- // or AMS's FgsStartTempAllowList.
- mAllowStartFgs = mService.isAllowlistedForFgsStartLocked(info.uid);
- }
- }
-
- /** A controller to generate error dialogs in {@link ProcessRecord} */
- class ErrorDialogController {
- /** dialogs being displayed due to crash */
- private List<AppErrorDialog> mCrashDialogs;
- /** dialogs being displayed due to app not responding */
- private List<AppNotRespondingDialog> mAnrDialogs;
- /** dialogs displayed due to strict mode violation */
- private List<StrictModeViolationDialog> mViolationDialogs;
- /** current wait for debugger dialog */
- private AppWaitingForDebuggerDialog mWaitDialog;
-
- boolean hasCrashDialogs() {
- return mCrashDialogs != null;
- }
-
- boolean hasAnrDialogs() {
- return mAnrDialogs != null;
- }
-
- boolean hasViolationDialogs() {
- return mViolationDialogs != null;
- }
-
- boolean hasDebugWaitingDialog() {
- return mWaitDialog != null;
- }
-
- void clearAllErrorDialogs() {
- clearCrashDialogs();
- clearAnrDialogs();
- clearViolationDialogs();
- clearWaitingDialog();
- }
-
- void clearCrashDialogs() {
- clearCrashDialogs(true /* needDismiss */);
- }
-
- void clearCrashDialogs(boolean needDismiss) {
- if (mCrashDialogs == null) {
- return;
- }
- if (needDismiss) {
- forAllDialogs(mCrashDialogs, Dialog::dismiss);
- }
- mCrashDialogs = null;
- }
-
- void clearAnrDialogs() {
- if (mAnrDialogs == null) {
- return;
- }
- forAllDialogs(mAnrDialogs, Dialog::dismiss);
- mAnrDialogs = null;
- }
-
- void clearViolationDialogs() {
- if (mViolationDialogs == null) {
- return;
- }
- forAllDialogs(mViolationDialogs, Dialog::dismiss);
- mViolationDialogs = null;
- }
-
- void clearWaitingDialog() {
- if (mWaitDialog == null) {
- return;
- }
- mWaitDialog.dismiss();
- mWaitDialog = null;
- }
-
- void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) {
- for (int i = dialogs.size() - 1; i >= 0; i--) {
- c.accept(dialogs.get(i));
- }
- }
-
- void showCrashDialogs(AppErrorDialog.Data data) {
- List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
- mCrashDialogs = new ArrayList<>();
- for (int i = contexts.size() - 1; i >= 0; i--) {
- final Context c = contexts.get(i);
- mCrashDialogs.add(new AppErrorDialog(c, mService, data));
- }
- mService.mUiHandler.post(() -> {
- List<AppErrorDialog> dialogs;
- synchronized (mService) {
- dialogs = mCrashDialogs;
- }
- if (dialogs != null) {
- forAllDialogs(dialogs, Dialog::show);
- }
- });
- }
-
- void showAnrDialogs(AppNotRespondingDialog.Data data) {
- List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */);
- mAnrDialogs = new ArrayList<>();
- for (int i = contexts.size() - 1; i >= 0; i--) {
- final Context c = contexts.get(i);
- mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
- }
- mService.mUiHandler.post(() -> {
- List<AppNotRespondingDialog> dialogs;
- synchronized (mService) {
- dialogs = mAnrDialogs;
- }
- if (dialogs != null) {
- forAllDialogs(dialogs, Dialog::show);
- }
- });
- }
-
- void showViolationDialogs(AppErrorResult res) {
- List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
- mViolationDialogs = new ArrayList<>();
- for (int i = contexts.size() - 1; i >= 0; i--) {
- final Context c = contexts.get(i);
- mViolationDialogs.add(
- new StrictModeViolationDialog(c, mService, res, ProcessRecord.this));
- }
- mService.mUiHandler.post(() -> {
- List<StrictModeViolationDialog> dialogs;
- synchronized (mService) {
- dialogs = mViolationDialogs;
- }
- if (dialogs != null) {
- forAllDialogs(dialogs, Dialog::show);
- }
- });
- }
-
- void showDebugWaitingDialogs() {
- List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */);
- final Context c = contexts.get(0);
- mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, ProcessRecord.this);
-
- mService.mUiHandler.post(() -> {
- Dialog dialog;
- synchronized (mService) {
- dialog = mWaitDialog;
- }
- if (dialog != null) {
- dialog.show();
- }
- });
- }
-
- /**
- * Helper function to collect contexts from crashed app located displays
- *
- * @param lastUsedOnly Sets to {@code true} to indicate to only get last used context.
- * Sets to {@code false} to collect contexts from crashed app located
- * displays.
- *
- * @return display context list
- */
- private List<Context> getDisplayContexts(boolean lastUsedOnly) {
- List<Context> displayContexts = new ArrayList<>();
- if (!lastUsedOnly) {
- mWindowProcessController.getDisplayContextsWithErrorDialogs(displayContexts);
- }
- // If there is no foreground window display, fallback to last used display.
- if (displayContexts.isEmpty() || lastUsedOnly) {
- displayContexts.add(mService.mWmInternal != null
- ? mService.mWmInternal.getTopFocusedDisplayUiContext()
- : mService.mUiContext);
- }
- return displayContexts;
- }
+ return mService.mProcessList.getLruProcessesLOSP();
}
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
new file mode 100644
index 0000000..5c3bf60
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -0,0 +1,444 @@
+/*
+ * 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.am;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * The state info of all services in the process.
+ */
+final class ProcessServiceRecord {
+ /**
+ * Are there any client services with activities?
+ */
+ private boolean mHasClientActivities;
+
+ /**
+ * Running any services that are foreground?
+ */
+ private boolean mHasForegroundServices;
+
+ /**
+ * Service that applied current connectionGroup/Importance.
+ */
+ private ServiceRecord mConnectionService;
+
+ /**
+ * Last group set by a connection.
+ */
+ private int mConnectionGroup;
+
+ /**
+ * Last importance set by a connection.
+ */
+ private int mConnectionImportance;
+
+ /**
+ * Type of foreground service, if there is a foreground service.
+ */
+ private int mFgServiceTypes;
+
+ /**
+ * Last reported foreground service types.
+ */
+ private int mRepFgServiceTypes;
+
+ /**
+ * Bound using BIND_ABOVE_CLIENT, so want to be lower.
+ */
+ private boolean mHasAboveClient;
+
+ /**
+ * Bound using BIND_TREAT_LIKE_ACTIVITY.
+ */
+ private boolean mTreatLikeActivity;
+
+ /**
+ * Do we need to be executing services in the foreground?
+ */
+ private boolean mExecServicesFg;
+
+ /**
+ * App is allowed to manage allowlists such as temporary Power Save mode allowlist.
+ */
+ boolean mAllowlistManager;
+
+ /**
+ * All ServiceRecord running in this process.
+ */
+ private final ArraySet<ServiceRecord> mServices = new ArraySet<>();
+
+ /**
+ * Services that are currently executing code (need to remain foreground).
+ */
+ private final ArraySet<ServiceRecord> mExecutingServices = new ArraySet<>();
+
+ /**
+ * All ConnectionRecord this process holds.
+ */
+ private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>();
+
+ /**
+ * A set of UIDs of all bound clients.
+ */
+ private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
+
+ final ProcessRecord mApp;
+
+ private final ActivityManagerService mService;
+
+ ProcessServiceRecord(ProcessRecord app) {
+ mApp = app;
+ mService = app.mService;
+ }
+
+ void setHasClientActivities(boolean hasClientActivities) {
+ mHasClientActivities = hasClientActivities;
+ mApp.getWindowProcessController().setHasClientActivities(hasClientActivities);
+ }
+
+ boolean hasClientActivities() {
+ return mHasClientActivities;
+ }
+
+ void setHasForegroundServices(boolean hasForegroundServices, int fgServiceTypes) {
+ mHasForegroundServices = hasForegroundServices;
+ mFgServiceTypes = fgServiceTypes;
+ mApp.getWindowProcessController().setHasForegroundServices(hasForegroundServices);
+ }
+
+ boolean hasForegroundServices() {
+ return mHasForegroundServices;
+ }
+
+ int getForegroundServiceTypes() {
+ return mHasForegroundServices ? mFgServiceTypes : 0;
+ }
+
+ int getReportedForegroundServiceTypes() {
+ return mRepFgServiceTypes;
+ }
+
+ void setReportedForegroundServiceTypes(int foregroundServiceTypes) {
+ mRepFgServiceTypes = foregroundServiceTypes;
+ }
+
+ ServiceRecord getConnectionService() {
+ return mConnectionService;
+ }
+
+ void setConnectionService(ServiceRecord connectionService) {
+ mConnectionService = connectionService;
+ }
+
+ int getConnectionGroup() {
+ return mConnectionGroup;
+ }
+
+ void setConnectionGroup(int connectionGroup) {
+ mConnectionGroup = connectionGroup;
+ }
+
+ int getConnectionImportance() {
+ return mConnectionImportance;
+ }
+
+ void setConnectionImportance(int connectionImportance) {
+ mConnectionImportance = connectionImportance;
+ }
+
+ void updateHasAboveClientLocked() {
+ mHasAboveClient = false;
+ for (int i = mConnections.size() - 1; i >= 0; i--) {
+ ConnectionRecord cr = mConnections.valueAt(i);
+ if ((cr.flags & Context.BIND_ABOVE_CLIENT) != 0) {
+ mHasAboveClient = true;
+ break;
+ }
+ }
+ }
+
+ void setHasAboveClient(boolean hasAboveClient) {
+ mHasAboveClient = hasAboveClient;
+ }
+
+ boolean hasAboveClient() {
+ return mHasAboveClient;
+ }
+
+ int modifyRawOomAdj(int adj) {
+ if (mHasAboveClient) {
+ // If this process has bound to any services with BIND_ABOVE_CLIENT,
+ // then we need to drop its adjustment to be lower than the service's
+ // in order to honor the request. We want to drop it by one adjustment
+ // level... but there is special meaning applied to various levels so
+ // we will skip some of them.
+ if (adj < ProcessList.FOREGROUND_APP_ADJ) {
+ // System process will not get dropped, ever
+ } else if (adj < ProcessList.VISIBLE_APP_ADJ) {
+ adj = ProcessList.VISIBLE_APP_ADJ;
+ } else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) {
+ adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ } else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+ adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+ } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
+ adj = ProcessList.CACHED_APP_MIN_ADJ;
+ } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
+ adj++;
+ }
+ }
+ return adj;
+ }
+
+ boolean isTreatedLikeActivity() {
+ return mTreatLikeActivity;
+ }
+
+ void setTreatLikeActivity(boolean treatLikeActivity) {
+ mTreatLikeActivity = treatLikeActivity;
+ }
+
+ boolean shouldExecServicesFg() {
+ return mExecServicesFg;
+ }
+
+ void setExecServicesFg(boolean execServicesFg) {
+ mExecServicesFg = execServicesFg;
+ }
+
+ /**
+ * Records a service as running in the process. Note that this method does not actually start
+ * the service, but records the service as started for bookkeeping.
+ *
+ * @return true if the service was added, false otherwise.
+ */
+ boolean startService(ServiceRecord record) {
+ if (record == null) {
+ return false;
+ }
+ boolean added = mServices.add(record);
+ if (added && record.serviceInfo != null) {
+ mApp.getWindowProcessController().onServiceStarted(record.serviceInfo);
+ }
+ return added;
+ }
+
+ /**
+ * Records a service as stopped. Note that like {@link #startService(ServiceRecord)} this method
+ * does not actually stop the service, but records the service as stopped for bookkeeping.
+ *
+ * @return true if the service was removed, false otherwise.
+ */
+ boolean stopService(ServiceRecord record) {
+ return mServices.remove(record);
+ }
+
+ /**
+ * The same as calling {@link #stopService(ServiceRecord)} on all current running services.
+ */
+ void stopAllServices() {
+ mServices.clear();
+ }
+
+ /**
+ * Returns the number of services added with {@link #startService(ServiceRecord)} and not yet
+ * removed by a call to {@link #stopService(ServiceRecord)} or {@link #stopAllServices()}.
+ *
+ * @see #startService(ServiceRecord)
+ * @see #stopService(ServiceRecord)
+ */
+ int numberOfRunningServices() {
+ return mServices.size();
+ }
+
+ /**
+ * Returns the service at the specified {@code index}.
+ *
+ * @see #numberOfRunningServices()
+ */
+ ServiceRecord getRunningServiceAt(int index) {
+ return mServices.valueAt(index);
+ }
+
+ void startExecutingService(ServiceRecord service) {
+ mExecutingServices.add(service);
+ }
+
+ void stopExecutingService(ServiceRecord service) {
+ mExecutingServices.remove(service);
+ }
+
+ void stopAllExecutingServices() {
+ mExecutingServices.clear();
+ }
+
+ ServiceRecord getExecutingServiceAt(int index) {
+ return mExecutingServices.valueAt(index);
+ }
+
+ int numberOfExecutingServices() {
+ return mExecutingServices.size();
+ }
+
+ void addConnection(ConnectionRecord connection) {
+ mConnections.add(connection);
+ }
+
+ void removeConnection(ConnectionRecord connection) {
+ mConnections.remove(connection);
+ }
+
+ void removeAllConnections() {
+ mConnections.clear();
+ }
+
+ ConnectionRecord getConnectionAt(int index) {
+ return mConnections.valueAt(index);
+ }
+
+ int numberOfConnections() {
+ return mConnections.size();
+ }
+
+ void addBoundClientUid(int clientUid) {
+ mBoundClientUids.add(clientUid);
+ mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+ }
+
+ void updateBoundClientUids() {
+ if (mServices.isEmpty()) {
+ clearBoundClientUids();
+ return;
+ }
+ // grab a set of clientUids of all mConnections of all services
+ final ArraySet<Integer> boundClientUids = new ArraySet<>();
+ final int serviceCount = mServices.size();
+ for (int j = 0; j < serviceCount; j++) {
+ final ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns =
+ mServices.valueAt(j).getConnections();
+ final int size = conns.size();
+ for (int conni = 0; conni < size; conni++) {
+ ArrayList<ConnectionRecord> c = conns.valueAt(conni);
+ for (int i = 0; i < c.size(); i++) {
+ boundClientUids.add(c.get(i).clientUid);
+ }
+ }
+ }
+ mBoundClientUids = boundClientUids;
+ mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+ }
+
+ void addBoundClientUidsOfNewService(ServiceRecord sr) {
+ if (sr == null) {
+ return;
+ }
+ ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = sr.getConnections();
+ for (int conni = conns.size() - 1; conni >= 0; conni--) {
+ ArrayList<ConnectionRecord> c = conns.valueAt(conni);
+ for (int i = 0; i < c.size(); i++) {
+ mBoundClientUids.add(c.get(i).clientUid);
+ }
+ }
+ mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+ }
+
+ void clearBoundClientUids() {
+ mBoundClientUids.clear();
+ mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+ }
+
+ @GuardedBy("mService")
+ boolean incServiceCrashCountLocked(long now) {
+ final boolean procIsBoundForeground = mApp.mState.getCurProcState()
+ == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ boolean tryAgain = false;
+ // Bump up the crash count of any services currently running in the proc.
+ for (int i = numberOfRunningServices() - 1; i >= 0; i--) {
+ // Any services running in the application need to be placed
+ // back in the pending list.
+ ServiceRecord sr = getRunningServiceAt(i);
+ // If the service was restarted a while ago, then reset crash count, else increment it.
+ if (now > sr.restartTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
+ sr.crashCount = 1;
+ } else {
+ sr.crashCount++;
+ }
+ // Allow restarting for started or bound foreground services that are crashing.
+ // This includes wallpapers.
+ if (sr.crashCount < mService.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
+ && (sr.isForeground || procIsBoundForeground)) {
+ tryAgain = true;
+ }
+ }
+ return tryAgain;
+ }
+
+ @GuardedBy("mService")
+ void onCleanupApplicationRecordLocked() {
+ mTreatLikeActivity = false;
+ mHasAboveClient = false;
+ setHasClientActivities(false);
+ }
+
+ void dump(PrintWriter pw, String prefix, long nowUptime) {
+ if (mHasForegroundServices || mApp.mState.getForcingToImportant() != null) {
+ pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices);
+ pw.print(" forcingToImportant="); pw.println(mApp.mState.getForcingToImportant());
+ }
+ if (mHasClientActivities || mHasAboveClient || mTreatLikeActivity) {
+ pw.print(prefix); pw.print("hasClientActivities="); pw.print(mHasClientActivities);
+ pw.print(" hasAboveClient="); pw.print(mHasAboveClient);
+ pw.print(" treatLikeActivity="); pw.println(mTreatLikeActivity);
+ }
+ if (mConnectionService != null || mConnectionGroup != 0) {
+ pw.print(prefix); pw.print("connectionGroup="); pw.print(mConnectionGroup);
+ pw.print(" Importance="); pw.print(mConnectionImportance);
+ pw.print(" Service="); pw.println(mConnectionService);
+ }
+ if (mAllowlistManager) {
+ pw.print(prefix); pw.print("allowlistManager="); pw.println(mAllowlistManager);
+ }
+ if (mServices.size() > 0) {
+ pw.print(prefix); pw.println("Services:");
+ for (int i = 0, size = mServices.size(); i < size; i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(mServices.valueAt(i));
+ }
+ }
+ if (mExecutingServices.size() > 0) {
+ pw.print(prefix); pw.print("Executing Services (fg=");
+ pw.print(mExecServicesFg); pw.println(")");
+ for (int i = 0, size = mExecutingServices.size(); i < size; i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(mExecutingServices.valueAt(i));
+ }
+ }
+ if (mConnections.size() > 0) {
+ pw.print(prefix); pw.println("mConnections:");
+ for (int i = 0, size = mConnections.size(); i < size; i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(mConnections.valueAt(i));
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
new file mode 100644
index 0000000..e1a153d
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -0,0 +1,1337 @@
+/*
+ * 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.am;
+
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_COMPANION_APP;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROC_STATE;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_UID;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED;
+import static com.android.server.am.ActiveServices.fgsCodeToString;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ProcessRecord.TAG;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
+
+
+import java.io.PrintWriter;
+
+/**
+ * The state info of the process, including proc state, oom adj score, et al.
+ */
+final class ProcessStateRecord {
+ private final ProcessRecord mApp;
+ private final ActivityManagerService mService;
+ private final ActivityManagerGlobalLock mProcLock;
+
+ /**
+ * Maximum OOM adjustment for this process.
+ */
+ @GuardedBy("mService")
+ private int mMaxAdj = ProcessList.UNKNOWN_ADJ;
+
+ /**
+ * Current OOM unlimited adjustment for this process.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurRawAdj = ProcessList.INVALID_ADJ;
+
+ /**
+ * Last set OOM unlimited adjustment for this process.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetRawAdj = ProcessList.INVALID_ADJ;
+
+ /**
+ * Current OOM adjustment for this process.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurAdj = ProcessList.INVALID_ADJ;
+
+ /**
+ * Last set OOM adjustment for this process.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetAdj = ProcessList.INVALID_ADJ;
+
+ /**
+ * The last adjustment that was verified as actually being set.
+ */
+ @GuardedBy("mService")
+ private int mVerifiedAdj = ProcessList.INVALID_ADJ;
+
+ /**
+ * Current capability flags of this process.
+ * For example, PROCESS_CAPABILITY_FOREGROUND_LOCATION is one capability.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurCapability = PROCESS_CAPABILITY_NONE;
+
+ /**
+ * Last set capability flags.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetCapability = PROCESS_CAPABILITY_NONE;
+
+ /**
+ * Currently desired scheduling class.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+
+ /**
+ * Last set to background scheduling class.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+
+ /**
+ * Currently computed process state.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurProcState = PROCESS_STATE_NONEXISTENT;
+
+ /**
+ * Last reported process state.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mRepProcState = PROCESS_STATE_NONEXISTENT;
+
+ /**
+ * Temp state during computation.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurRawProcState = PROCESS_STATE_NONEXISTENT;
+
+ /**
+ * Last set process state in process tracker.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetProcState = PROCESS_STATE_NONEXISTENT;
+
+ /**
+ * Last time mSetProcState changed.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mLastStateTime;
+
+ /**
+ * Previous priority value if we're switching to non-SCHED_OTHER.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSavedPriority;
+
+ /**
+ * Process currently is on the service B list.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mServiceB;
+
+ /**
+ * We are forcing to service B list due to its RAM use.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mServiceHighRam;
+
+ /**
+ * Has this process not been in a cached state since last idle?
+ */
+ @GuardedBy("mProcLock")
+ private boolean mNotCachedSinceIdle;
+
+ /**
+ * Are there any started services running in this process?
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mHasStartedServices;
+
+ /**
+ * Running any activities that are foreground?
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mHasForegroundActivities;
+
+ /**
+ * Last reported foreground activities.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mRepForegroundActivities;
+
+ /**
+ * Has UI been shown in this process since it was started?
+ */
+ @GuardedBy("mService")
+ private boolean mHasShownUi;
+
+ /**
+ * Is this process currently showing a non-activity UI that the user
+ * is interacting with? E.g. The status bar when it is expanded, but
+ * not when it is minimized. When true the
+ * process will be set to use the ProcessList#SCHED_GROUP_TOP_APP
+ * scheduling group to boost performance.
+ */
+ @GuardedBy("mService")
+ private boolean mHasTopUi;
+
+ /**
+ * Is the process currently showing a non-activity UI that
+ * overlays on-top of activity UIs on screen. E.g. display a window
+ * of type android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
+ * When true the process will oom adj score will be set to
+ * ProcessList#PERCEPTIBLE_APP_ADJ at minimum to reduce the chance
+ * of the process getting killed.
+ */
+ @GuardedBy("mService")
+ private boolean mHasOverlayUi;
+
+ /**
+ * Is the process currently running a RemoteAnimation? When true
+ * the process will be set to use the
+ * ProcessList#SCHED_GROUP_TOP_APP scheduling group to boost
+ * performance, as well as oom adj score will be set to
+ * ProcessList#VISIBLE_APP_ADJ at minimum to reduce the chance
+ * of the process getting killed.
+ */
+ @GuardedBy("mService")
+ private boolean mRunningRemoteAnimation;
+
+ /**
+ * Keep track of whether we changed 'mSetAdj'.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mProcStateChanged;
+
+ /**
+ * Whether we have told usage stats about it being an interaction.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mReportedInteraction;
+
+ /**
+ * The time we sent the last interaction event.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mInteractionEventTime;
+
+ /**
+ * When we became foreground for interaction purposes.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mFgInteractionTime;
+
+ /**
+ * Token that is forcing this process to be important.
+ */
+ @GuardedBy("mService")
+ private Object mForcingToImportant;
+
+ /**
+ * Sequence id for identifying oom_adj assignment cycles.
+ */
+ @GuardedBy("mService")
+ private int mAdjSeq;
+
+ /**
+ * Sequence id for identifying oom_adj assignment cycles.
+ */
+ @GuardedBy("mService")
+ private int mCompletedAdjSeq;
+
+ /**
+ * Whether this app has encountered a cycle in the most recent update.
+ */
+ @GuardedBy("mService")
+ private boolean mContainsCycle;
+
+ /**
+ * When (uptime) the process last became unimportant.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mWhenUnimportant;
+
+ /**
+ * The last time the process was in the TOP state or greater.
+ */
+ @GuardedBy("mService")
+ private long mLastTopTime;
+
+ /**
+ * Is this an empty background process?
+ */
+ @GuardedBy("mService")
+ private boolean mEmpty;
+
+ /**
+ * Is this a cached process?
+ */
+ @GuardedBy("mService")
+ private boolean mCached;
+
+ /**
+ * This is a system process, but not currently showing UI.
+ */
+ @GuardedBy("mService")
+ private boolean mSystemNoUi;
+
+ /**
+ * If the proc state is PROCESS_STATE_BOUND_FOREGROUND_SERVICE or above, it can start FGS.
+ * It must obtain the proc state from a persistent/top process or FGS, not transitive.
+ */
+ @GuardedBy("mService")
+ private int mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
+
+ @GuardedBy("mService")
+ private final ArraySet<Binder> mBackgroundFgsStartTokens = new ArraySet<>();
+
+ /**
+ * Does the process has permission to start FGS from background.
+ */
+ @GuardedBy("mService")
+ private @ActiveServices.FgsFeatureRetCode int mAllowStartFgsByPermission;
+
+ /**
+ * Can this process start FGS from background?
+ * If this process has the ability to start FGS from background, this ability can be passed to
+ * another process through service binding.
+ */
+ @GuardedBy("mService")
+ private @ActiveServices.FgsFeatureRetCode int mAllowStartFgs;
+
+ /**
+ * Debugging: primary thing impacting oom_adj.
+ */
+ @GuardedBy("mService")
+ private String mAdjType;
+
+ /**
+ * Debugging: adj code to report to app.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mAdjTypeCode;
+
+ /**
+ * Debugging: option dependent object.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private Object mAdjSource;
+
+ /**
+ * Debugging: proc state of mAdjSource's process.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mAdjSourceProcState;
+
+ /**
+ * Debugging: target component impacting oom_adj.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private Object mAdjTarget;
+
+ /**
+ * Approximates the usage count of the app, used for cache re-ranking by CacheOomRanker.
+ *
+ * Counts the number of times the process is re-added to the cache (i.e. setCached(false);
+ * setCached(true)). This over counts, as setCached is sometimes reset while remaining in the
+ * cache. However, this happens uniformly across processes, so ranking is not affected.
+ */
+ @GuardedBy("mService")
+ private int mCacheOomRankerUseCount;
+
+ /**
+ * Whether or not this process is reachable from given process.
+ */
+ @GuardedBy("mService")
+ private boolean mReachable;
+
+ // Below are the cached task info for OomAdjuster only
+ private static final int VALUE_INVALID = -1;
+ private static final int VALUE_FALSE = 0;
+ private static final int VALUE_TRUE = 1;
+
+ @GuardedBy("mService")
+ private int mCachedHasActivities = VALUE_INVALID;
+ @GuardedBy("mService")
+ private int mCachedIsHeavyWeight = VALUE_INVALID;
+ @GuardedBy("mService")
+ private int mCachedHasVisibleActivities = VALUE_INVALID;
+ @GuardedBy("mService")
+ private int mCachedIsHomeProcess = VALUE_INVALID;
+ @GuardedBy("mService")
+ private int mCachedIsPreviousProcess = VALUE_INVALID;
+ @GuardedBy("mService")
+ private int mCachedHasRecentTasks = VALUE_INVALID;
+ @GuardedBy("mService")
+ private int mCachedIsReceivingBroadcast = VALUE_INVALID;
+
+ @GuardedBy("mService")
+ private int mCachedAdj = ProcessList.INVALID_ADJ;
+ @GuardedBy("mService")
+ private boolean mCachedForegroundActivities = false;
+ @GuardedBy("mService")
+ private int mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ @GuardedBy("mService")
+ private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+
+ ProcessStateRecord(ProcessRecord app) {
+ mApp = app;
+ mService = app.mService;
+ mProcLock = mService.mProcLock;
+ setAllowStartFgsByPermission();
+ }
+
+ void init(long now) {
+ mLastStateTime = now;
+ }
+
+ @GuardedBy("mService")
+ void setMaxAdj(int maxAdj) {
+ mMaxAdj = maxAdj;
+ }
+
+ @GuardedBy("mService")
+ int getMaxAdj() {
+ return mMaxAdj;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurRawAdj(int curRawAdj) {
+ mCurRawAdj = curRawAdj;
+ mApp.getWindowProcessController().setPerceptible(
+ curRawAdj <= ProcessList.PERCEPTIBLE_APP_ADJ);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurRawAdj() {
+ return mCurRawAdj;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetRawAdj(int setRawAdj) {
+ mSetRawAdj = setRawAdj;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetRawAdj() {
+ return mSetRawAdj;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurAdj(int curAdj) {
+ mCurAdj = curAdj;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurAdj() {
+ return mCurAdj;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetAdj(int setAdj) {
+ mSetAdj = setAdj;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetAdj() {
+ return mSetAdj;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetAdjWithServices() {
+ if (mSetAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+ if (mHasStartedServices) {
+ return ProcessList.SERVICE_B_ADJ;
+ }
+ }
+ return mSetAdj;
+ }
+
+ @GuardedBy("mService")
+ void setVerifiedAdj(int verifiedAdj) {
+ mVerifiedAdj = verifiedAdj;
+ }
+
+ @GuardedBy("mService")
+ int getVerifiedAdj() {
+ return mVerifiedAdj;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurCapability(int curCapability) {
+ mCurCapability = curCapability;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurCapability() {
+ return mCurCapability;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetCapability(int setCapability) {
+ mSetCapability = setCapability;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetCapability() {
+ return mSetCapability;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurrentSchedulingGroup(int curSchedGroup) {
+ mCurSchedGroup = curSchedGroup;
+ mApp.getWindowProcessController().setCurrentSchedulingGroup(curSchedGroup);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurrentSchedulingGroup() {
+ return mCurSchedGroup;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetSchedGroup(int setSchedGroup) {
+ mSetSchedGroup = setSchedGroup;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetSchedGroup() {
+ return mSetSchedGroup;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurProcState(int curProcState) {
+ mCurProcState = curProcState;
+ mApp.getWindowProcessController().setCurrentProcState(mCurProcState);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurProcState() {
+ return mCurProcState;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurRawProcState(int curRawProcState) {
+ mCurRawProcState = curRawProcState;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurRawProcState() {
+ return mCurRawProcState;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setReportedProcState(int repProcState) {
+ mRepProcState = repProcState;
+ mApp.getPkgList().forEachPackage((pkgName, holder) ->
+ FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+ mApp.uid, mApp.processName, pkgName,
+ ActivityManager.processStateAmToProto(mRepProcState),
+ holder.appVersion)
+ );
+ mApp.getWindowProcessController().setReportedProcState(repProcState);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getReportedProcState() {
+ return mRepProcState;
+ }
+
+ @GuardedBy("mService")
+ void forceProcessStateUpTo(int newState) {
+ if (mRepProcState > newState) {
+ synchronized (mProcLock) {
+ mRepProcState = newState;
+ setCurProcState(newState);
+ setCurRawProcState(newState);
+ mApp.getPkgList().forEachPackage((pkgName, holder) ->
+ FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+ mApp.uid, mApp.processName, pkgName,
+ ActivityManager.processStateAmToProto(mRepProcState),
+ holder.appVersion)
+ );
+ }
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetProcState(int setProcState) {
+ mSetProcState = setProcState;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetProcState() {
+ return mSetProcState;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setLastStateTime(long lastStateTime) {
+ mLastStateTime = lastStateTime;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getLastStateTime() {
+ return mLastStateTime;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSavedPriority(int savedPriority) {
+ mSavedPriority = savedPriority;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSavedPriority() {
+ return mSavedPriority;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setServiceB(boolean serviceb) {
+ mServiceB = serviceb;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isServiceB() {
+ return mServiceB;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setServiceHighRam(boolean serviceHighRam) {
+ mServiceHighRam = serviceHighRam;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isServiceHighRam() {
+ return mServiceHighRam;
+ }
+
+ @GuardedBy("mProcLock")
+ void setNotCachedSinceIdle(boolean notCachedSinceIdle) {
+ mNotCachedSinceIdle = notCachedSinceIdle;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean isNotCachedSinceIdle() {
+ return mNotCachedSinceIdle;
+ }
+
+ @GuardedBy("mProcLock")
+ void setHasStartedServices(boolean hasStartedServices) {
+ mHasStartedServices = hasStartedServices;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean hasStartedServices() {
+ return mHasStartedServices;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setHasForegroundActivities(boolean hasForegroundActivities) {
+ mHasForegroundActivities = hasForegroundActivities;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean hasForegroundActivities() {
+ return mHasForegroundActivities;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setRepForegroundActivities(boolean repForegroundActivities) {
+ mRepForegroundActivities = repForegroundActivities;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean hasRepForegroundActivities() {
+ return mRepForegroundActivities;
+ }
+
+ @GuardedBy("mService")
+ void setHasShownUi(boolean hasShownUi) {
+ mHasShownUi = hasShownUi;
+ }
+
+ @GuardedBy("mService")
+ boolean hasShownUi() {
+ return mHasShownUi;
+ }
+
+ @GuardedBy("mService")
+ void setHasTopUi(boolean hasTopUi) {
+ mHasTopUi = hasTopUi;
+ mApp.getWindowProcessController().setHasTopUi(hasTopUi);
+ }
+
+ @GuardedBy("mService")
+ boolean hasTopUi() {
+ return mHasTopUi;
+ }
+
+ @GuardedBy("mService")
+ void setHasOverlayUi(boolean hasOverlayUi) {
+ mHasOverlayUi = hasOverlayUi;
+ mApp.getWindowProcessController().setHasOverlayUi(hasOverlayUi);
+ }
+
+ @GuardedBy("mService")
+ boolean hasOverlayUi() {
+ return mHasOverlayUi;
+ }
+
+ @GuardedBy("mService")
+ boolean isRunningRemoteAnimation() {
+ return mRunningRemoteAnimation;
+ }
+
+ @GuardedBy("mService")
+ void setRunningRemoteAnimation(boolean runningRemoteAnimation) {
+ if (mRunningRemoteAnimation == runningRemoteAnimation) {
+ return;
+ }
+ mRunningRemoteAnimation = runningRemoteAnimation;
+ if (DEBUG_OOM_ADJ) {
+ Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
+ + " for pid=" + mApp.getPid());
+ }
+ mService.updateOomAdjLocked(mApp, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setProcStateChanged(boolean procStateChanged) {
+ mProcStateChanged = procStateChanged;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean hasProcStateChanged() {
+ return mProcStateChanged;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setReportedInteraction(boolean reportedInteraction) {
+ mReportedInteraction = reportedInteraction;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean hasReportedInteraction() {
+ return mReportedInteraction;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setInteractionEventTime(long interactionEventTime) {
+ mInteractionEventTime = interactionEventTime;
+ mApp.getWindowProcessController().setInteractionEventTime(interactionEventTime);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getInteractionEventTime() {
+ return mInteractionEventTime;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setFgInteractionTime(long fgInteractionTime) {
+ mFgInteractionTime = fgInteractionTime;
+ mApp.getWindowProcessController().setFgInteractionTime(fgInteractionTime);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getFgInteractionTime() {
+ return mFgInteractionTime;
+ }
+
+ @GuardedBy("mService")
+ void setForcingToImportant(Object forcingToImportant) {
+ mForcingToImportant = forcingToImportant;
+ }
+
+ @GuardedBy("mService")
+ Object getForcingToImportant() {
+ return mForcingToImportant;
+ }
+
+ @GuardedBy("mService")
+ void setAdjSeq(int adjSeq) {
+ mAdjSeq = adjSeq;
+ }
+
+ @GuardedBy("mService")
+ void decAdjSeq() {
+ mAdjSeq--;
+ }
+
+ @GuardedBy("mService")
+ int getAdjSeq() {
+ return mAdjSeq;
+ }
+
+ @GuardedBy("mService")
+ void setCompletedAdjSeq(int completedAdjSeq) {
+ mCompletedAdjSeq = completedAdjSeq;
+ }
+
+ @GuardedBy("mService")
+ void decCompletedAdjSeq() {
+ mCompletedAdjSeq--;
+ }
+
+ @GuardedBy("mService")
+ int getCompletedAdjSeq() {
+ return mCompletedAdjSeq;
+ }
+
+ @GuardedBy("mService")
+ void setContainsCycle(boolean containsCycle) {
+ mContainsCycle = containsCycle;
+ }
+
+ @GuardedBy("mService")
+ boolean containsCycle() {
+ return mContainsCycle;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setWhenUnimportant(long whenUnimportant) {
+ mWhenUnimportant = whenUnimportant;
+ mApp.getWindowProcessController().setWhenUnimportant(whenUnimportant);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getWhenUnimportant() {
+ return mWhenUnimportant;
+ }
+
+ @GuardedBy("mService")
+ void setLastTopTime(long lastTopTime) {
+ mLastTopTime = lastTopTime;
+ }
+
+ @GuardedBy("mService")
+ long getLastTopTime() {
+ return mLastTopTime;
+ }
+
+ @GuardedBy("mService")
+ void setEmpty(boolean empty) {
+ mEmpty = empty;
+ }
+
+ @GuardedBy("mService")
+ boolean isEmpty() {
+ return mEmpty;
+ }
+
+ @GuardedBy("mService")
+ void setCached(boolean cached) {
+ if (mCached != cached) {
+ mCached = cached;
+ if (cached) {
+ ++mCacheOomRankerUseCount;
+ }
+ }
+ }
+
+ @GuardedBy("mService")
+ boolean isCached() {
+ return mCached;
+ }
+
+ @GuardedBy("mService")
+ int getCacheOomRankerUseCount() {
+ return mCacheOomRankerUseCount;
+ }
+
+ @GuardedBy("mService")
+ void setSystemNoUi(boolean systemNoUi) {
+ mSystemNoUi = systemNoUi;
+ }
+
+ @GuardedBy("mService")
+ boolean isSystemNoUi() {
+ return mSystemNoUi;
+ }
+
+ @GuardedBy("mService")
+ void setAdjType(String adjType) {
+ mAdjType = adjType;
+ }
+
+ @GuardedBy("mService")
+ String getAdjType() {
+ return mAdjType;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setAdjTypeCode(int adjTypeCode) {
+ mAdjTypeCode = adjTypeCode;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getAdjTypeCode() {
+ return mAdjTypeCode;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setAdjSource(Object adjSource) {
+ mAdjSource = adjSource;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ Object getAdjSource() {
+ return mAdjSource;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setAdjSourceProcState(int adjSourceProcState) {
+ mAdjSourceProcState = adjSourceProcState;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getAdjSourceProcState() {
+ return mAdjSourceProcState;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setAdjTarget(Object adjTarget) {
+ mAdjTarget = adjTarget;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ Object getAdjTarget() {
+ return mAdjTarget;
+ }
+
+ @GuardedBy("mService")
+ boolean isReachable() {
+ return mReachable;
+ }
+
+ @GuardedBy("mService")
+ void setReachable(boolean reachable) {
+ mReachable = reachable;
+ }
+
+ @GuardedBy("mService")
+ void resetCachedInfo() {
+ mCachedHasActivities = VALUE_INVALID;
+ mCachedIsHeavyWeight = VALUE_INVALID;
+ mCachedHasVisibleActivities = VALUE_INVALID;
+ mCachedIsHomeProcess = VALUE_INVALID;
+ mCachedIsPreviousProcess = VALUE_INVALID;
+ mCachedHasRecentTasks = VALUE_INVALID;
+ mCachedIsReceivingBroadcast = VALUE_INVALID;
+ mCachedAdj = ProcessList.INVALID_ADJ;
+ mCachedForegroundActivities = false;
+ mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedHasActivities() {
+ if (mCachedHasActivities == VALUE_INVALID) {
+ mCachedHasActivities = mApp.getWindowProcessController().hasActivities() ? VALUE_TRUE
+ : VALUE_FALSE;
+ }
+ return mCachedHasActivities == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedIsHeavyWeight() {
+ if (mCachedIsHeavyWeight == VALUE_INVALID) {
+ mCachedIsHeavyWeight = mApp.getWindowProcessController().isHeavyWeightProcess()
+ ? VALUE_TRUE : VALUE_FALSE;
+ }
+ return mCachedIsHeavyWeight == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedHasVisibleActivities() {
+ if (mCachedHasVisibleActivities == VALUE_INVALID) {
+ mCachedHasVisibleActivities = mApp.getWindowProcessController().hasVisibleActivities()
+ ? VALUE_TRUE : VALUE_FALSE;
+ }
+ return mCachedHasVisibleActivities == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedIsHomeProcess() {
+ if (mCachedIsHomeProcess == VALUE_INVALID) {
+ if (mApp.getWindowProcessController().isHomeProcess()) {
+ mCachedIsHomeProcess = VALUE_TRUE;
+ mService.mAppProfiler.mHasHomeProcess = true;
+ } else {
+ mCachedIsHomeProcess = VALUE_FALSE;
+ }
+ }
+ return mCachedIsHomeProcess == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedIsPreviousProcess() {
+ if (mCachedIsPreviousProcess == VALUE_INVALID) {
+ if (mApp.getWindowProcessController().isPreviousProcess()) {
+ mCachedIsPreviousProcess = VALUE_TRUE;
+ mService.mAppProfiler.mHasPreviousProcess = true;
+ } else {
+ mCachedIsPreviousProcess = VALUE_FALSE;
+ }
+ }
+ return mCachedIsPreviousProcess == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedHasRecentTasks() {
+ if (mCachedHasRecentTasks == VALUE_INVALID) {
+ mCachedHasRecentTasks = mApp.getWindowProcessController().hasRecentTasks()
+ ? VALUE_TRUE : VALUE_FALSE;
+ }
+ return mCachedHasRecentTasks == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedIsReceivingBroadcast(ArraySet<BroadcastQueue> tmpQueue) {
+ if (mCachedIsReceivingBroadcast == VALUE_INVALID) {
+ tmpQueue.clear();
+ mCachedIsReceivingBroadcast = mService.isReceivingBroadcastLocked(mApp, tmpQueue)
+ ? VALUE_TRUE : VALUE_FALSE;
+ if (mCachedIsReceivingBroadcast == VALUE_TRUE) {
+ mCachedSchedGroup = tmpQueue.contains(mService.mFgBroadcastQueue)
+ ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+ }
+ }
+ return mCachedIsReceivingBroadcast == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
+ void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
+ int adj, boolean foregroundActivities, int procState, int schedGroup, int appUid,
+ int logUid, int processCurTop) {
+ if (mCachedAdj != ProcessList.INVALID_ADJ) {
+ return;
+ }
+ callback.initialize(mApp, adj, foregroundActivities, procState, schedGroup, appUid, logUid,
+ processCurTop);
+ final int minLayer = Math.min(ProcessList.VISIBLE_APP_LAYER_MAX,
+ mApp.getWindowProcessController().computeOomAdjFromActivities(callback));
+
+ mCachedAdj = callback.adj;
+ mCachedForegroundActivities = callback.foregroundActivities;
+ mCachedProcState = callback.procState;
+ mCachedSchedGroup = callback.schedGroup;
+
+ if (mCachedAdj == ProcessList.VISIBLE_APP_ADJ) {
+ mCachedAdj += minLayer;
+ }
+ }
+
+ @GuardedBy("mService")
+ int getCachedAdj() {
+ return mCachedAdj;
+ }
+
+ @GuardedBy("mService")
+ boolean getCachedForegroundActivities() {
+ return mCachedForegroundActivities;
+ }
+
+ @GuardedBy("mService")
+ int getCachedProcState() {
+ return mCachedProcState;
+ }
+
+ @GuardedBy("mService")
+ int getCachedSchedGroup() {
+ return mCachedSchedGroup;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ public String makeAdjReason() {
+ if (mAdjSource != null || mAdjTarget != null) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append(' ');
+ if (mAdjTarget instanceof ComponentName) {
+ sb.append(((ComponentName) mAdjTarget).flattenToShortString());
+ } else if (mAdjTarget != null) {
+ sb.append(mAdjTarget.toString());
+ } else {
+ sb.append("{null}");
+ }
+ sb.append("<=");
+ if (mAdjSource instanceof ProcessRecord) {
+ sb.append("Proc{");
+ sb.append(((ProcessRecord) mAdjSource).toShortString());
+ sb.append("}");
+ } else if (mAdjSource != null) {
+ sb.append(mAdjSource.toString());
+ } else {
+ sb.append("{null}");
+ }
+ return sb.toString();
+ }
+ return null;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void onCleanupApplicationRecordLSP() {
+ setHasForegroundActivities(false);
+ mHasShownUi = false;
+ mForcingToImportant = null;
+ mCurRawAdj = mSetRawAdj = mCurAdj = mSetAdj = mVerifiedAdj = ProcessList.INVALID_ADJ;
+ mCurCapability = mSetCapability = PROCESS_CAPABILITY_NONE;
+ mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ mCurProcState = mRepProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
+ PROCESS_STATE_NONEXISTENT;
+ }
+
+ @GuardedBy("mService")
+ void addAllowBackgroundFgsStartsToken(Binder entity) {
+ mBackgroundFgsStartTokens.add(entity);
+ }
+
+ @GuardedBy("mService")
+ void removeAllowBackgroundFgsStartsToken(Binder entity) {
+ mBackgroundFgsStartTokens.remove(entity);
+ }
+
+ @GuardedBy("mService")
+ boolean areBackgroundFgsStartsAllowedByToken() {
+ return !mBackgroundFgsStartTokens.isEmpty();
+ }
+
+ @GuardedBy("mService")
+ void resetAllowStartFgs() {
+ mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
+ mAllowStartFgs = mAllowStartFgsByPermission;
+ }
+
+ @GuardedBy("mService")
+ void bumpAllowStartFgsState(int newProcState) {
+ if (newProcState < mAllowStartFgsState) {
+ mAllowStartFgsState = newProcState;
+ }
+ }
+
+ @GuardedBy("mService")
+ void setAllowStartFgsState(int allowStartFgsState) {
+ mAllowStartFgsState = allowStartFgsState;
+ }
+
+ @GuardedBy("mService")
+ boolean isAllowedStartFgsState() {
+ return mAllowStartFgsState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ }
+
+ @GuardedBy("mService")
+ void setAllowStartFgsByPermission() {
+ int ret = FGS_FEATURE_DENIED;
+ if (ret == FGS_FEATURE_DENIED) {
+ boolean isSystem = false;
+ final int uid = UserHandle.getAppId(mApp.info.uid);
+ switch (uid) {
+ case ROOT_UID:
+ case SYSTEM_UID:
+ case NFC_UID:
+ case SHELL_UID:
+ isSystem = true;
+ break;
+ default:
+ isSystem = false;
+ break;
+ }
+
+ if (isSystem) {
+ ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_UID;
+ }
+ }
+
+ if (ret == FGS_FEATURE_DENIED) {
+ if (ActivityManager.checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
+ mApp.info.uid, -1, true) == PERMISSION_GRANTED) {
+ ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION;
+ } else if (ActivityManager.checkComponentPermission(
+ START_FOREGROUND_SERVICES_FROM_BACKGROUND,
+ mApp.info.uid, -1, true) == PERMISSION_GRANTED) {
+ ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION;
+ } else if (ActivityManager.checkComponentPermission(SYSTEM_ALERT_WINDOW,
+ mApp.info.uid, -1, true) == PERMISSION_GRANTED) {
+ ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION;
+ }
+ }
+ mAllowStartFgs = mAllowStartFgsByPermission = ret;
+ }
+
+ @GuardedBy("mService")
+ void setAllowStartFgs() {
+ if (mAllowStartFgs != FGS_FEATURE_DENIED) {
+ return;
+ }
+ if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+ if (isAllowedStartFgsState()) {
+ mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROC_STATE;
+ }
+ }
+
+ if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+ // Is the calling UID a device owner app?
+ if (mService.mInternal != null) {
+ if (mService.mInternal.isDeviceOwner(mApp.info.uid)) {
+ mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER;
+ }
+ }
+ }
+
+ if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+ if (mService.mInternal != null) {
+ final boolean isCompanionApp = mService.mInternal.isAssociatedCompanionApp(
+ UserHandle.getUserId(mApp.info.uid), mApp.info.uid);
+ if (isCompanionApp) {
+ mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_COMPANION_APP;
+ }
+ }
+ }
+
+ if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+ // Is the calling UID a profile owner app?
+ if (mService.mInternal != null) {
+ if (mService.mInternal.isProfileOwner(mApp.info.uid)) {
+ mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER;
+ }
+ }
+ }
+
+ if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+ // uid is on DeviceIdleController's user/system allowlist
+ // or AMS's FgsStartTempAllowList.
+ if (mService.isAllowlistedForFgsStartLOSP(mApp.info.uid)) {
+ mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST;
+ }
+ }
+ }
+
+ @GuardedBy("mService")
+ void setAllowStartFgs(@ActiveServices.FgsFeatureRetCode int allowStartFgs) {
+ mAllowStartFgs = allowStartFgs;
+ }
+
+ @GuardedBy("mService")
+ @ActiveServices.FgsFeatureRetCode int getAllowedStartFgs() {
+ return mAllowStartFgs;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void dump(PrintWriter pw, String prefix, long nowUptime) {
+ if (mReportedInteraction || mFgInteractionTime != 0) {
+ pw.print(prefix); pw.print("reportedInteraction=");
+ pw.print(mReportedInteraction);
+ if (mInteractionEventTime != 0) {
+ pw.print(" time=");
+ TimeUtils.formatDuration(mInteractionEventTime, SystemClock.elapsedRealtime(), pw);
+ }
+ if (mFgInteractionTime != 0) {
+ pw.print(" fgInteractionTime=");
+ TimeUtils.formatDuration(mFgInteractionTime, SystemClock.elapsedRealtime(), pw);
+ }
+ pw.println();
+ }
+ pw.print(prefix); pw.print("adjSeq="); pw.print(mAdjSeq);
+ pw.print(" lruSeq="); pw.println(mApp.getLruSeq());
+ pw.print(prefix); pw.print("oom adj: max="); pw.print(mMaxAdj);
+ pw.print(" curRaw="); pw.print(mCurRawAdj);
+ pw.print(" setRaw="); pw.print(mSetRawAdj);
+ pw.print(" cur="); pw.print(mCurAdj);
+ pw.print(" set="); pw.println(mSetAdj);
+ pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
+ pw.print(" setSchedGroup="); pw.print(mSetSchedGroup);
+ pw.print(" systemNoUi="); pw.print(mSystemNoUi);
+ pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
+ pw.print(" mRepProcState="); pw.print(mRepProcState);
+ pw.print(" setProcState="); pw.print(mSetProcState);
+ pw.print(" lastStateTime=");
+ TimeUtils.formatDuration(getLastStateTime(), nowUptime, pw);
+ pw.println();
+ pw.print(prefix); pw.print("curCapability=");
+ ActivityManager.printCapabilitiesFull(pw, mCurCapability);
+ pw.print(" setCapability=");
+ ActivityManager.printCapabilitiesFull(pw, mSetCapability);
+ pw.println();
+ pw.print(prefix); pw.print("allowStartFgsState=");
+ pw.println(mAllowStartFgsState);
+ if (mAllowStartFgs != FGS_FEATURE_DENIED) {
+ pw.print(prefix); pw.print("allowStartFgs=");
+ pw.println(fgsCodeToString(mAllowStartFgs));
+ }
+ if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) {
+ pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi);
+ pw.print(" pendingUiClean="); pw.print(mApp.mProfile.hasPendingUiClean());
+ }
+ pw.print(prefix); pw.print("cached="); pw.print(mCached);
+ pw.print(" empty="); pw.println(mEmpty);
+ if (mServiceB) {
+ pw.print(prefix); pw.print("serviceb="); pw.print(mServiceB);
+ pw.print(" serviceHighRam="); pw.println(mServiceHighRam);
+ }
+ if (mNotCachedSinceIdle) {
+ pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle);
+ pw.print(" initialIdlePss="); pw.println(mApp.mProfile.getInitialIdlePss());
+ }
+ if (hasTopUi() || hasOverlayUi() || mRunningRemoteAnimation) {
+ pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi());
+ pw.print(" hasOverlayUi="); pw.print(hasOverlayUi());
+ pw.print(" runningRemoteAnimation="); pw.println(mRunningRemoteAnimation);
+ }
+ if (mHasForegroundActivities || mRepForegroundActivities) {
+ pw.print(prefix);
+ pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities);
+ pw.print(" (rep="); pw.print(mRepForegroundActivities); pw.println(")");
+ }
+ if (mSetProcState > ActivityManager.PROCESS_STATE_SERVICE) {
+ pw.print(prefix);
+ pw.print(" whenUnimportant=");
+ TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw);
+ pw.println();
+ }
+ if (mLastTopTime > 0) {
+ pw.print(prefix); pw.print("lastTopTime=");
+ TimeUtils.formatDuration(mLastTopTime, nowUptime, pw);
+ pw.println();
+ }
+ if (mHasStartedServices) {
+ pw.print(prefix); pw.print("hasStartedServices="); pw.println(mHasStartedServices);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index c10a078..34a0c1b 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -313,8 +313,8 @@
private void scheduleRequestPssAllProcs(boolean always, boolean memLowered) {
mAm.mHandler.post(() -> {
- synchronized (mAm) {
- mAm.mAppProfiler.requestPssAllProcsLocked(
+ synchronized (mAm.mProcLock) {
+ mAm.mAppProfiler.requestPssAllProcsLPr(
SystemClock.uptimeMillis(), always, memLowered);
}
});
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index 29c1657..072eba5 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.ComponentName.WithComponentName;
import android.os.Binder;
@@ -23,6 +24,7 @@
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
+
import com.android.internal.os.TransferPipe;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
@@ -372,10 +374,11 @@
*/
private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw,
final ContentProviderRecord r, String[] args, boolean dumpAll) {
+ final IApplicationThread thread = r.proc != null ? r.proc.getThread() : null;
for (String s: args) {
if (!dumpAll && s.contains("--proto")) {
- if (r.proc != null && r.proc.thread != null) {
- dumpToTransferPipe(null , fd, pw, r, args);
+ if (thread != null) {
+ dumpToTransferPipe(null , fd, pw, r, thread, args);
}
return;
}
@@ -386,7 +389,7 @@
pw.print(r);
pw.print(" pid=");
if (r.proc != null) {
- pw.println(r.proc.pid);
+ pw.println(r.proc.getPid());
} else {
pw.println("(not running)");
}
@@ -394,10 +397,10 @@
r.dump(pw, innerPrefix, true);
}
}
- if (r.proc != null && r.proc.thread != null) {
+ if (thread != null) {
pw.println(" Client:");
pw.flush();
- dumpToTransferPipe(" ", fd, pw, r, args);
+ dumpToTransferPipe(" ", fd, pw, r, thread, args);
}
}
@@ -420,8 +423,9 @@
// Only dump the first provider, since we are dumping in proto format
for (int i = 0; i < providers.size(); i++) {
final ContentProviderRecord r = providers.get(i);
- if (r.proc != null && r.proc.thread != null) {
- dumpToTransferPipe(null, fd, pw, r, newArgs);
+ IApplicationThread thread;
+ if (r.proc != null && (thread = r.proc.getThread()) != null) {
+ dumpToTransferPipe(null, fd, pw, r, thread, newArgs);
return true;
}
}
@@ -433,11 +437,11 @@
* any meta string (e.g., provider info, indentation) written to the file descriptor.
*/
private void dumpToTransferPipe(String prefix, FileDescriptor fd, PrintWriter pw,
- final ContentProviderRecord r, String[] args) {
+ final ContentProviderRecord r, final IApplicationThread thread, String[] args) {
try {
TransferPipe tp = new TransferPipe();
try {
- r.proc.thread.dumpProvider(
+ thread.dumpProvider(
tp.getWriteFd(), r.provider.asBinder(), args);
tp.setBufferPrefix(prefix);
// Short timeout, since blocking here can
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8a1b4e3..485087d 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -23,6 +23,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.Nullable;
+import android.app.IApplicationThread;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -281,7 +282,7 @@
proto.write(ServiceRecordProto.SHORT_NAME, this.shortInstanceName);
proto.write(ServiceRecordProto.IS_RUNNING, app != null);
if (app != null) {
- proto.write(ServiceRecordProto.PID, app.pid);
+ proto.write(ServiceRecordProto.PID, app.getPid());
}
if (intent != null) {
intent.getIntent().dumpDebug(proto, ServiceRecordProto.INTENT, false, true, false,
@@ -582,13 +583,14 @@
restartTracker.setRestarting(true, memFactor, now);
}
- public void setProcess(ProcessRecord _proc) {
- if (_proc != null) {
+ public void setProcess(ProcessRecord proc, IApplicationThread thread, int pid,
+ UidRecord uidRecord) {
+ if (proc != null) {
// We're starting a new process for this service, but a previous one is allowed to start
// background activities. Remove that ability now (unless the new process is the same as
// the previous one, which is a common case).
if (mAppForAllowingBgActivityStartsByStart != null) {
- if (mAppForAllowingBgActivityStartsByStart != _proc) {
+ if (mAppForAllowingBgActivityStartsByStart != proc) {
mAppForAllowingBgActivityStartsByStart
.removeAllowBackgroundActivityStartsToken(this);
ams.mHandler.removeCallbacks(mCleanUpAllowBgActivityStartsByStartCallback);
@@ -596,34 +598,35 @@
}
// Make sure the cleanup callback knows about the new process.
mAppForAllowingBgActivityStartsByStart = mIsAllowedBgActivityStartsByStart
- ? _proc : null;
+ ? proc : null;
if (mIsAllowedBgActivityStartsByStart
|| mIsAllowedBgActivityStartsByBinding) {
- _proc.addOrUpdateAllowBackgroundActivityStartsToken(this,
+ proc.addOrUpdateAllowBackgroundActivityStartsToken(this,
getExclusiveOriginatingToken());
} else {
- _proc.removeAllowBackgroundActivityStartsToken(this);
+ proc.removeAllowBackgroundActivityStartsToken(this);
}
if (mIsAllowedBgFgsStartsByBinding) {
- _proc.addAllowBackgroundFgsStartsToken(this);
+ proc.mState.addAllowBackgroundFgsStartsToken(this);
} else {
- _proc.removeAllowBackgroundFgsStartsToken(this);
+ proc.mState.removeAllowBackgroundFgsStartsToken(this);
}
}
- if (app != null && app != _proc) {
+ if (app != null && app != proc) {
// If the old app is allowed to start bg activities because of a service start, leave it
// that way until the cleanup callback runs. Otherwise we can remove its bg activity
// start ability immediately (it can't be bound now).
if (!mIsAllowedBgActivityStartsByStart) {
app.removeAllowBackgroundActivityStartsToken(this);
}
- app.updateBoundClientUids();
+ app.mServices.updateBoundClientUids();
}
- app = _proc;
- if (pendingConnectionGroup > 0 && _proc != null) {
- _proc.connectionService = this;
- _proc.connectionGroup = pendingConnectionGroup;
- _proc.connectionImportance = pendingConnectionImportance;
+ app = proc;
+ if (pendingConnectionGroup > 0 && proc != null) {
+ final ProcessServiceRecord psr = proc.mServices;
+ psr.setConnectionService(this);
+ psr.setConnectionGroup(pendingConnectionGroup);
+ psr.setConnectionImportance(pendingConnectionImportance);
pendingConnectionGroup = pendingConnectionImportance = 0;
}
if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
@@ -631,7 +634,7 @@
ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
for (int i = 0; i < cr.size(); i++) {
final ConnectionRecord conn = cr.get(i);
- if (_proc != null) {
+ if (proc != null) {
conn.startAssociationIfNeeded();
} else {
conn.stopAssociation();
@@ -639,8 +642,8 @@
}
}
}
- if (_proc != null) {
- _proc.updateBoundClientUids();
+ if (proc != null) {
+ proc.mServices.updateBoundClientUids();
}
}
@@ -658,7 +661,7 @@
// if we have a process attached, add bound client uid of this connection to it
if (app != null) {
- app.addBoundClientUid(c.clientUid);
+ app.mServices.addBoundClientUid(c.clientUid);
}
}
@@ -666,7 +669,7 @@
connections.remove(binder);
// if we have a process attached, tell it to update the state of bound clients
if (app != null) {
- app.updateBoundClientUids();
+ app.mServices.updateBoundClientUids();
}
}
@@ -820,9 +823,9 @@
return;
}
if (mIsAllowedBgFgsStartsByBinding) {
- app.addAllowBackgroundFgsStartsToken(this);
+ app.mState.addAllowBackgroundFgsStartsToken(this);
} else {
- app.removeAllowBackgroundFgsStartsToken(this);
+ app.mState.removeAllowBackgroundFgsStartsToken(this);
}
}
@@ -937,7 +940,7 @@
public void postNotification() {
final int appUid = appInfo.uid;
- final int appPid = app.pid;
+ final int appPid = app.getPid();
if (foregroundId != 0 && foregroundNoti != null) {
// Do asynchronous communication with notification manager to
// avoid deadlocks.
@@ -1057,7 +1060,7 @@
final String localPackageName = packageName;
final int localForegroundId = foregroundId;
final int appUid = appInfo.uid;
- final int appPid = app != null ? app.pid : 0;
+ final int appPid = app != null ? app.getPid() : 0;
ams.mHandler.post(new Runnable() {
public void run() {
NotificationManagerInternal nm = LocalServices.getService(
diff --git a/services/core/java/com/android/server/am/StrictModeViolationDialog.java b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
index 22e7faa..4cc1fc1 100644
--- a/services/core/java/com/android/server/am/StrictModeViolationDialog.java
+++ b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
@@ -49,7 +49,7 @@
mProc = app;
mResult = result;
CharSequence name;
- if ((app.getPkgList().size() == 1)
+ if (app.getPkgList().size() == 1
&& (name = context.getPackageManager().getApplicationLabel(app.info)) != null) {
setMessage(res.getString(
com.android.internal.R.string.smv_application,
@@ -67,7 +67,7 @@
res.getText(com.android.internal.R.string.dlg_ok),
mHandler.obtainMessage(ACTION_OK));
- if (app.errorReportReceiver != null) {
+ if (app.mErrorState.getErrorReportReceiver() != null) {
setButton(DialogInterface.BUTTON_NEGATIVE,
res.getText(com.android.internal.R.string.report),
mHandler.obtainMessage(ACTION_OK_AND_REPORT));
@@ -84,9 +84,9 @@
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
- synchronized (mService) {
+ synchronized (mService.mProcLock) {
if (mProc != null) {
- mProc.getDialogController().clearViolationDialogs();
+ mProc.mErrorState.getDialogController().clearViolationDialogs();
}
}
mResult.set(msg.what);
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index b3150d1..df65ee6 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -204,16 +204,18 @@
} else {
UidRecord validateUid = mValidateUids.get(item.uid);
if (validateUid == null) {
- validateUid = new UidRecord(item.uid);
+ validateUid = new UidRecord(item.uid, null);
mValidateUids.put(item.uid, validateUid);
}
if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
- validateUid.idle = true;
+ validateUid.setIdle(true);
} else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
- validateUid.idle = false;
+ validateUid.setIdle(false);
}
- validateUid.setCurProcState(validateUid.setProcState = item.procState);
- validateUid.curCapability = validateUid.setCapability = item.capability;
+ validateUid.setSetProcState(item.procState);
+ validateUid.setCurProcState(item.procState);
+ validateUid.setSetCapability(item.capability);
+ validateUid.setCurCapability(item.capability);
validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
}
}
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 36d22bf..2fb662f 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -26,27 +26,58 @@
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
+import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.server.am.UidObserverController.ChangeRecord;
+import java.util.function.Consumer;
+
/**
* Overall information about a uid that has actively running processes.
*/
public final class UidRecord {
- final int uid;
- int mCurProcState;
- int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- int curCapability;
- int setCapability;
- long lastBackgroundTime;
- boolean ephemeral;
- boolean foregroundServices;
- boolean mCurAllowlist;
- boolean mSetAllowlist;
- boolean idle;
- boolean setIdle;
- int numProcs;
- ArraySet<ProcessRecord> procRecords = new ArraySet<>();
+ private final ActivityManagerService mService;
+ private final ActivityManagerGlobalLock mProcLock;
+ private final int mUid;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurProcState;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mCurCapability;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetCapability;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mLastBackgroundTime;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mEphemeral;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mForegroundServices;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mCurAllowList;;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mSetAllowList;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mIdle;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private boolean mSetIdle;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mNumProcs;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private ArraySet<ProcessRecord> mProcRecords = new ArraySet<>();
/**
* Sequence number associated with the {@link #mCurProcState}. This is incremented using
@@ -111,32 +142,168 @@
// UidObserverController is the only thing that should modify this.
final ChangeRecord pendingChange = new ChangeRecord();
- int lastReportedChange;
+ @GuardedBy("mService")
+ private int mLastReportedChange;
- public UidRecord(int _uid) {
- uid = _uid;
- idle = true;
+ public UidRecord(int uid, ActivityManagerService service) {
+ mUid = uid;
+ mService = service;
+ mProcLock = service != null ? service.mProcLock : null;
+ mIdle = true;
reset();
}
- public int getCurProcState() {
+ int getUid() {
+ return mUid;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurProcState() {
return mCurProcState;
}
- public void setCurProcState(int curProcState) {
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurProcState(int curProcState) {
mCurProcState = curProcState;
}
- public void reset() {
- setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
- foregroundServices = false;
- curCapability = 0;
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetProcState() {
+ return mSetProcState;
+ }
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetProcState(int setProcState) {
+ mSetProcState = setProcState;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurCapability() {
+ return mCurCapability;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurCapability(int curCapability) {
+ mCurCapability = curCapability;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetCapability() {
+ return mSetCapability;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetCapability(int setCapability) {
+ mSetCapability = setCapability;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getLastBackgroundTime() {
+ return mLastBackgroundTime;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setLastBackgroundTime(long lastBackgroundTime) {
+ mLastBackgroundTime = lastBackgroundTime;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isEphemeral() {
+ return mEphemeral;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setEphemeral(boolean ephemeral) {
+ mEphemeral = ephemeral;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean hasForegroundServices() {
+ return mForegroundServices;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setForegroundServices(boolean foregroundServices) {
+ mForegroundServices = foregroundServices;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isCurAllowListed() {
+ return mCurAllowList;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setCurAllowListed(boolean curAllowList) {
+ mCurAllowList = curAllowList;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isSetAllowListed() {
+ return mSetAllowList;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetAllowListed(boolean setAllowlist) {
+ mSetAllowList = setAllowlist;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isIdle() {
+ return mIdle;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setIdle(boolean idle) {
+ mIdle = idle;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean isSetIdle() {
+ return mSetIdle;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setSetIdle(boolean setIdle) {
+ mSetIdle = setIdle;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getNumOfProcs() {
+ return mProcRecords.size();
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ void forEachProcess(Consumer<ProcessRecord> callback) {
+ for (int i = mProcRecords.size() - 1; i >= 0; i--) {
+ callback.accept(mProcRecords.valueAt(i));
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void addProcess(ProcessRecord app) {
+ mProcRecords.add(app);
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void removeProcess(ProcessRecord app) {
+ mProcRecords.remove(app);
+ }
+
+ @GuardedBy("mService")
+ void setLastReportedChange(int lastReportedChange) {
+ mLastReportedChange = lastReportedChange;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void reset() {
+ setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ mForegroundServices = false;
+ mCurCapability = 0;
}
public void updateHasInternetPermission() {
hasInternetPermission = ActivityManager.checkUidPermission(Manifest.permission.INTERNET,
- uid) == PackageManager.PERMISSION_GRANTED;
+ mUid) == PackageManager.PERMISSION_GRANTED;
}
/**
@@ -153,19 +320,19 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
- proto.write(UidRecordProto.UID, uid);
+ proto.write(UidRecordProto.UID, mUid);
proto.write(UidRecordProto.CURRENT, ProcessList.makeProcStateProtoEnum(mCurProcState));
- proto.write(UidRecordProto.EPHEMERAL, ephemeral);
- proto.write(UidRecordProto.FG_SERVICES, foregroundServices);
- proto.write(UidRecordProto.WHILELIST, mCurAllowlist);
+ proto.write(UidRecordProto.EPHEMERAL, mEphemeral);
+ proto.write(UidRecordProto.FG_SERVICES, mForegroundServices);
+ proto.write(UidRecordProto.WHILELIST, mCurAllowList);
ProtoUtils.toDuration(proto, UidRecordProto.LAST_BACKGROUND_TIME,
- lastBackgroundTime, SystemClock.elapsedRealtime());
- proto.write(UidRecordProto.IDLE, idle);
- if (lastReportedChange != 0) {
+ mLastBackgroundTime, SystemClock.elapsedRealtime());
+ proto.write(UidRecordProto.IDLE, mIdle);
+ if (mLastReportedChange != 0) {
ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidRecordProto.LAST_REPORTED_CHANGES,
- lastReportedChange, ORIG_ENUMS, PROTO_ENUMS);
+ mLastReportedChange, ORIG_ENUMS, PROTO_ENUMS);
}
- proto.write(UidRecordProto.NUM_PROCS, numProcs);
+ proto.write(UidRecordProto.NUM_PROCS, mNumProcs);
long seqToken = proto.start(UidRecordProto.NETWORK_STATE_UPDATE);
proto.write(UidRecordProto.ProcStateSequence.CURURENT, curProcStateSeq);
@@ -182,54 +349,54 @@
sb.append("UidRecord{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(' ');
- UserHandle.formatUid(sb, uid);
+ UserHandle.formatUid(sb, mUid);
sb.append(' ');
sb.append(ProcessList.makeProcStateString(mCurProcState));
- if (ephemeral) {
+ if (mEphemeral) {
sb.append(" ephemeral");
}
- if (foregroundServices) {
+ if (mForegroundServices) {
sb.append(" fgServices");
}
- if (mCurAllowlist) {
+ if (mCurAllowList) {
sb.append(" allowlist");
}
- if (lastBackgroundTime > 0) {
+ if (mLastBackgroundTime > 0) {
sb.append(" bg:");
- TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mLastBackgroundTime, sb);
}
- if (idle) {
+ if (mIdle) {
sb.append(" idle");
}
- if (lastReportedChange != 0) {
+ if (mLastReportedChange != 0) {
sb.append(" change:");
boolean printed = false;
- if ((lastReportedChange & CHANGE_GONE) != 0) {
+ if ((mLastReportedChange & CHANGE_GONE) != 0) {
printed = true;
sb.append("gone");
}
- if ((lastReportedChange & CHANGE_IDLE) != 0) {
+ if ((mLastReportedChange & CHANGE_IDLE) != 0) {
if (printed) {
sb.append("|");
}
printed = true;
sb.append("idle");
}
- if ((lastReportedChange & CHANGE_ACTIVE) != 0) {
+ if ((mLastReportedChange & CHANGE_ACTIVE) != 0) {
if (printed) {
sb.append("|");
}
printed = true;
sb.append("active");
}
- if ((lastReportedChange & CHANGE_CACHED) != 0) {
+ if ((mLastReportedChange & CHANGE_CACHED) != 0) {
if (printed) {
sb.append("|");
}
printed = true;
sb.append("cached");
}
- if ((lastReportedChange & CHANGE_UNCACHED) != 0) {
+ if ((mLastReportedChange & CHANGE_UNCACHED) != 0) {
if (printed) {
sb.append("|");
}
@@ -237,7 +404,7 @@
}
}
sb.append(" procs:");
- sb.append(numProcs);
+ sb.append(mNumProcs);
sb.append(" seq(");
sb.append(curProcStateSeq);
sb.append(",");
diff --git a/services/core/java/com/android/server/graphics/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
similarity index 97%
rename from services/core/java/com/android/server/graphics/GameManagerService.java
rename to services/core/java/com/android/server/app/GameManagerService.java
index 876f02f..3acad49 100644
--- a/services/core/java/com/android/server/graphics/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
import android.annotation.NonNull;
+import android.app.GameManager;
+import android.app.GameManager.GameMode;
+import android.app.IGameManagerService;
import android.content.Context;
-import android.graphics.GameManager;
-import android.graphics.GameManager.GameMode;
-import android.graphics.IGameManagerService;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
diff --git a/services/core/java/com/android/server/graphics/Settings.java b/services/core/java/com/android/server/app/Settings.java
similarity index 98%
rename from services/core/java/com/android/server/graphics/Settings.java
rename to services/core/java/com/android/server/app/Settings.java
index bbd84d0..ab367fb 100644
--- a/services/core/java/com/android/server/graphics/Settings.java
+++ b/services/core/java/com/android/server/app/Settings.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
-import android.graphics.GameManager;
+import android.app.GameManager;
import android.os.FileUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index fded85c..e97f0b4 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -18,11 +18,9 @@
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.ACTION_USER_ADDED;
-import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_REMOVED_FOR_ALL_USERS;
import static android.content.Intent.EXTRA_REPLACING;
-import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
import android.annotation.NonNull;
@@ -36,8 +34,9 @@
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
-import android.content.pm.UserInfo;
+import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Environment;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -48,16 +47,21 @@
import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
+import java.io.File;
import java.io.FileDescriptor;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
/**
* System service that manages app hibernation state, a state apps can enter that means they are
@@ -66,6 +70,11 @@
*/
public final class AppHibernationService extends SystemService {
private static final String TAG = "AppHibernationService";
+ private static final int PACKAGE_MATCH_FLAGS =
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS;
/**
* Lock for accessing any in-memory hibernation state
@@ -76,9 +85,13 @@
private final IActivityManager mIActivityManager;
private final UserManager mUserManager;
@GuardedBy("mLock")
- private final SparseArray<Map<String, UserPackageState>> mUserStates = new SparseArray<>();
+ private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>();
+ private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores =
+ new SparseArray<>();
@GuardedBy("mLock")
- private final Set<String> mGloballyHibernatedPackages = new ArraySet<>();
+ private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>();
+ private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore;
+ private final Injector mInjector;
/**
* Initializes the system service.
@@ -90,28 +103,22 @@
* @param context The system server context.
*/
public AppHibernationService(@NonNull Context context) {
- this(context, IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
- ActivityManager.getService(),
- context.getSystemService(UserManager.class));
+ this(new InjectorImpl(context));
}
@VisibleForTesting
- AppHibernationService(@NonNull Context context, IPackageManager packageManager,
- IActivityManager activityManager, UserManager userManager) {
- super(context);
- mContext = context;
- mIPackageManager = packageManager;
- mIActivityManager = activityManager;
- mUserManager = userManager;
+ AppHibernationService(@NonNull Injector injector) {
+ super(injector.getContext());
+ mContext = injector.getContext();
+ mIPackageManager = injector.getPackageManager();
+ mIActivityManager = injector.getActivityManager();
+ mUserManager = injector.getUserManager();
+ mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
+ mInjector = injector;
final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_USER_ADDED);
- intentFilter.addAction(ACTION_USER_REMOVED);
- userAllContext.registerReceiver(mBroadcastReceiver, intentFilter);
-
- intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_PACKAGE_ADDED);
intentFilter.addAction(ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
@@ -126,12 +133,10 @@
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
+ List<GlobalLevelState> states =
+ mGlobalLevelHibernationDiskStore.readHibernationStates();
synchronized (mLock) {
- final List<UserInfo> users = mUserManager.getUsers();
- // TODO: Pull from persistent disk storage. For now, just make from scratch.
- for (UserInfo user : users) {
- addUserPackageStatesL(user.id);
- }
+ initializeGlobalHibernationStates(states);
}
}
}
@@ -145,12 +150,14 @@
*/
boolean isHibernatingForUser(String packageName, int userId) {
userId = handleIncomingUser(userId, "isHibernating");
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user "
+ + userId);
+ return false;
+ }
synchronized (mLock) {
- final Map<String, UserPackageState> packageStates = mUserStates.get(userId);
- if (packageStates == null) {
- throw new IllegalArgumentException("No user associated with user id " + userId);
- }
- final UserPackageState pkgState = packageStates.get(packageName);
+ final Map<String, UserLevelState> packageStates = mUserStates.get(userId);
+ final UserLevelState pkgState = packageStates.get(packageName);
if (pkgState == null) {
throw new IllegalArgumentException(
String.format("Package %s is not installed for user %s",
@@ -168,7 +175,12 @@
*/
boolean isHibernatingGlobally(String packageName) {
synchronized (mLock) {
- return mGloballyHibernatedPackages.contains(packageName);
+ GlobalLevelState state = mGlobalHibernationStates.get(packageName);
+ if (state == null) {
+ throw new IllegalArgumentException(
+ String.format("Package %s is not installed", packageName));
+ }
+ return state.hibernated;
}
}
@@ -181,12 +193,14 @@
*/
void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
userId = handleIncomingUser(userId, "setHibernating");
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user "
+ + userId);
+ return;
+ }
synchronized (mLock) {
- if (!mUserStates.contains(userId)) {
- throw new IllegalArgumentException("No user associated with user id " + userId);
- }
- Map<String, UserPackageState> packageStates = mUserStates.get(userId);
- UserPackageState pkgState = packageStates.get(packageName);
+ final Map<String, UserLevelState> packageStates = mUserStates.get(userId);
+ final UserLevelState pkgState = packageStates.get(packageName);
if (pkgState == null) {
throw new IllegalArgumentException(
String.format("Package %s is not installed for user %s",
@@ -198,10 +212,12 @@
}
if (isHibernating) {
- hibernatePackageForUserL(packageName, userId, pkgState);
+ hibernatePackageForUser(packageName, userId, pkgState);
} else {
- unhibernatePackageForUserL(packageName, userId, pkgState);
+ unhibernatePackageForUser(packageName, userId, pkgState);
}
+ List<UserLevelState> states = new ArrayList<>(mUserStates.get(userId).values());
+ mUserDiskStores.get(userId).scheduleWriteHibernationStates(states);
}
}
@@ -213,25 +229,32 @@
* @param isHibernating new hibernation state
*/
void setHibernatingGlobally(String packageName, boolean isHibernating) {
- if (isHibernating != mGloballyHibernatedPackages.contains(packageName)) {
- synchronized (mLock) {
+ synchronized (mLock) {
+ GlobalLevelState state = mGlobalHibernationStates.get(packageName);
+ if (state == null) {
+ throw new IllegalArgumentException(
+ String.format("Package %s is not installed for any user", packageName));
+ }
+ if (state.hibernated != isHibernating) {
if (isHibernating) {
- hibernatePackageGloballyL(packageName);
+ hibernatePackageGlobally(packageName, state);
} else {
- unhibernatePackageGloballyL(packageName);
+ unhibernatePackageGlobally(packageName, state);
}
+ List<GlobalLevelState> states = new ArrayList<>(mGlobalHibernationStates.values());
+ mGlobalLevelHibernationDiskStore.scheduleWriteHibernationStates(states);
}
}
}
/**
* Put an app into hibernation for a given user, allowing user-level optimizations to occur.
- * The caller should hold {@link #mLock}
*
* @param pkgState package hibernation state
*/
- private void hibernatePackageForUserL(@NonNull String packageName, int userId,
- @NonNull UserPackageState pkgState) {
+ @GuardedBy("mLock")
+ private void hibernatePackageForUser(@NonNull String packageName, int userId,
+ @NonNull UserLevelState pkgState) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage");
final long caller = Binder.clearCallingIdentity();
try {
@@ -249,12 +272,13 @@
}
/**
- * Remove a package from hibernation for a given user. The caller should hold {@link #mLock}.
+ * Remove a package from hibernation for a given user.
*
* @param pkgState package hibernation state
*/
- private void unhibernatePackageForUserL(@NonNull String packageName, int userId,
- UserPackageState pkgState) {
+ @GuardedBy("mLock")
+ private void unhibernatePackageForUser(@NonNull String packageName, int userId,
+ UserLevelState pkgState) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage");
final long caller = Binder.clearCallingIdentity();
try {
@@ -271,60 +295,140 @@
/**
* Put a package into global hibernation, optimizing its storage at a package / APK level.
- * The caller should hold {@link #mLock}.
*/
- private void hibernatePackageGloballyL(@NonNull String packageName) {
+ @GuardedBy("mLock")
+ private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
// TODO(175830194): Delete vdex/odex when DexManager API is built out
- mGloballyHibernatedPackages.add(packageName);
+ state.hibernated = true;
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
/**
- * Unhibernate a package from global hibernation. The caller should hold {@link #mLock}.
+ * Unhibernate a package from global hibernation.
*/
- private void unhibernatePackageGloballyL(@NonNull String packageName) {
+ @GuardedBy("mLock")
+ private void unhibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackageGlobally");
- mGloballyHibernatedPackages.remove(packageName);
+ state.hibernated = false;
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
/**
- * Populates {@link #mUserStates} with the users installed packages. The caller should hold
- * {@link #mLock}.
+ * Initializes in-memory store of user-level hibernation states for the given user
*
* @param userId user id to add installed packages for
+ * @param diskStates states pulled from disk, if available
*/
- private void addUserPackageStatesL(int userId) {
- Map<String, UserPackageState> packages = new ArrayMap<>();
- List<PackageInfo> packageList;
+ @GuardedBy("mLock")
+ private void initializeUserHibernationStates(int userId,
+ @Nullable List<UserLevelState> diskStates) {
+ List<PackageInfo> packages;
try {
- packageList = mIPackageManager.getInstalledPackages(MATCH_ALL, userId).getList();
+ packages = mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId).getList();
} catch (RemoteException e) {
- throw new IllegalStateException("Package manager not available.", e);
+ throw new IllegalStateException("Package manager not available", e);
}
- for (int i = 0, size = packageList.size(); i < size; i++) {
- packages.put(packageList.get(i).packageName, new UserPackageState());
+ Map<String, UserLevelState> userLevelStates = new ArrayMap<>();
+
+ for (int i = 0, size = packages.size(); i < size; i++) {
+ String packageName = packages.get(i).packageName;
+ UserLevelState state = new UserLevelState();
+ state.packageName = packageName;
+ userLevelStates.put(packageName, state);
}
- mUserStates.put(userId, packages);
+
+ if (diskStates != null) {
+ Set<String> installedPackages = new ArraySet<>();
+ for (int i = 0, size = packages.size(); i < size; i++) {
+ installedPackages.add(packages.get(i).packageName);
+ }
+ for (int i = 0, size = diskStates.size(); i < size; i++) {
+ String packageName = diskStates.get(i).packageName;
+ if (!installedPackages.contains(packageName)) {
+ Slog.w(TAG, String.format(
+ "No hibernation state associated with package %s user %d. Maybe"
+ + "the package was uninstalled? ", packageName, userId));
+ continue;
+ }
+ userLevelStates.put(packageName, diskStates.get(i));
+ }
+ }
+ mUserStates.put(userId, userLevelStates);
}
- private void onUserAdded(int userId) {
- synchronized (mLock) {
- addUserPackageStatesL(userId);
+ /**
+ * Initialize in-memory store of global level hibernation states.
+ *
+ * @param diskStates global level hibernation states pulled from disk, if available
+ */
+ @GuardedBy("mLock")
+ private void initializeGlobalHibernationStates(@Nullable List<GlobalLevelState> diskStates) {
+ List<PackageInfo> packages;
+ try {
+ packages = mIPackageManager.getInstalledPackages(
+ PACKAGE_MATCH_FLAGS | MATCH_ANY_USER, 0 /* userId */).getList();
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Package manager not available", e);
+ }
+
+ for (int i = 0, size = packages.size(); i < size; i++) {
+ String packageName = packages.get(i).packageName;
+ GlobalLevelState state = new GlobalLevelState();
+ state.packageName = packageName;
+ mGlobalHibernationStates.put(packageName, state);
+ }
+ if (diskStates != null) {
+ Set<String> installedPackages = new ArraySet<>();
+ for (int i = 0, size = packages.size(); i < size; i++) {
+ installedPackages.add(packages.get(i).packageName);
+ }
+ for (int i = 0, size = diskStates.size(); i < size; i++) {
+ GlobalLevelState state = diskStates.get(i);
+ if (!installedPackages.contains(state.packageName)) {
+ Slog.w(TAG, String.format(
+ "No hibernation state associated with package %s. Maybe the "
+ + "package was uninstalled? ", state.packageName));
+ continue;
+ }
+ mGlobalHibernationStates.put(state.packageName, state);
+ }
}
}
- private void onUserRemoved(int userId) {
+ @Override
+ public void onUserUnlocking(@NonNull TargetUser user) {
+ int userId = user.getUserIdentifier();
+ HibernationStateDiskStore<UserLevelState> diskStore =
+ mInjector.getUserLevelDiskStore(userId);
+ mUserDiskStores.put(userId, diskStore);
+ List<UserLevelState> storedStates = diskStore.readHibernationStates();
synchronized (mLock) {
+ initializeUserHibernationStates(userId, storedStates);
+ }
+ }
+
+ @Override
+ public void onUserStopping(@NonNull TargetUser user) {
+ int userId = user.getUserIdentifier();
+ // TODO: Flush any scheduled writes to disk immediately on user stopping / power off.
+ synchronized (mLock) {
+ mUserDiskStores.remove(userId);
mUserStates.remove(userId);
}
}
private void onPackageAdded(@NonNull String packageName, int userId) {
synchronized (mLock) {
- mUserStates.get(userId).put(packageName, new UserPackageState());
+ UserLevelState userState = new UserLevelState();
+ userState.packageName = packageName;
+ mUserStates.get(userId).put(packageName, userState);
+ if (!mGlobalHibernationStates.containsKey(packageName)) {
+ GlobalLevelState globalState = new GlobalLevelState();
+ globalState.packageName = packageName;
+ mGlobalHibernationStates.put(packageName, globalState);
+ }
}
}
@@ -336,7 +440,7 @@
private void onPackageRemovedForAllUsers(@NonNull String packageName) {
synchronized (mLock) {
- mGloballyHibernatedPackages.remove(packageName);
+ mGlobalHibernationStates.remove(packageName);
}
}
@@ -395,7 +499,7 @@
}
}
- // Broadcast receiver for user and package add/removal events
+ // Broadcast receiver for package add/removal events
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -405,12 +509,6 @@
}
final String action = intent.getAction();
- if (ACTION_USER_ADDED.equals(action)) {
- onUserAdded(userId);
- }
- if (ACTION_USER_REMOVED.equals(action)) {
- onUserRemoved(userId);
- }
if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REMOVED.equals(action)) {
final String packageName = intent.getData().getSchemeSpecificPart();
if (intent.getBooleanExtra(EXTRA_REPLACING, false)) {
@@ -443,10 +541,66 @@
}
/**
- * Data class that contains hibernation state info of a package for a user.
+ * Dependency injector for {@link #AppHibernationService)}.
*/
- private static final class UserPackageState {
- public boolean hibernated;
- // TODO: Track whether hibernation is exempted by the user
+ interface Injector {
+ Context getContext();
+
+ IPackageManager getPackageManager();
+
+ IActivityManager getActivityManager();
+
+ UserManager getUserManager();
+
+ HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore();
+
+ HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId);
+ }
+
+ private static final class InjectorImpl implements Injector {
+ private static final String HIBERNATION_DIR_NAME = "hibernation";
+ private final Context mContext;
+ private final ScheduledExecutorService mScheduledExecutorService;
+ private final UserLevelHibernationProto mUserLevelHibernationProto;
+
+ InjectorImpl(Context context) {
+ mContext = context;
+ mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+ mUserLevelHibernationProto = new UserLevelHibernationProto();
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public IPackageManager getPackageManager() {
+ return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ }
+
+ @Override
+ public IActivityManager getActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mContext.getSystemService(UserManager.class);
+ }
+
+ @Override
+ public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
+ File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME);
+ return new HibernationStateDiskStore<>(
+ dir, new GlobalLevelHibernationProto(), mScheduledExecutorService);
+ }
+
+ @Override
+ public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
+ File dir = new File(Environment.getDataSystemCeDirectory(userId), HIBERNATION_DIR_NAME);
+ return new HibernationStateDiskStore<>(
+ dir, mUserLevelHibernationProto, mScheduledExecutorService);
+ }
}
}
diff --git a/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java
new file mode 100644
index 0000000..79e995b
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java
@@ -0,0 +1,78 @@
+/*
+ * 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.apphibernation;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads and writes protos for {@link GlobalLevelState} hiberation states.
+ */
+final class GlobalLevelHibernationProto implements ProtoReadWriter<List<GlobalLevelState>> {
+ private static final String TAG = "GlobalLevelHibernationProtoReadWriter";
+
+ @Override
+ public void writeToProto(@NonNull ProtoOutputStream stream,
+ @NonNull List<GlobalLevelState> data) {
+ for (int i = 0, size = data.size(); i < size; i++) {
+ long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE);
+ GlobalLevelState state = data.get(i);
+ stream.write(GlobalLevelHibernationStateProto.PACKAGE_NAME, state.packageName);
+ stream.write(GlobalLevelHibernationStateProto.HIBERNATED, state.hibernated);
+ stream.end(token);
+ }
+ }
+
+ @Override
+ public @Nullable List<GlobalLevelState> readFromProto(@NonNull ProtoInputStream stream)
+ throws IOException {
+ List<GlobalLevelState> list = new ArrayList<>();
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (stream.getFieldNumber()
+ != (int) GlobalLevelHibernationStatesProto.HIBERNATION_STATE) {
+ continue;
+ }
+ GlobalLevelState state = new GlobalLevelState();
+ long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE);
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) GlobalLevelHibernationStateProto.PACKAGE_NAME:
+ state.packageName =
+ stream.readString(GlobalLevelHibernationStateProto.PACKAGE_NAME);
+ break;
+ case (int) GlobalLevelHibernationStateProto.HIBERNATED:
+ state.hibernated =
+ stream.readBoolean(GlobalLevelHibernationStateProto.HIBERNATED);
+ break;
+ default:
+ Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber());
+ }
+ }
+ stream.end(token);
+ list.add(state);
+ }
+ return list;
+ }
+}
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java
similarity index 73%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to services/core/java/com/android/server/apphibernation/GlobalLevelState.java
index 19b20f2..4f75675 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+package com.android.server.apphibernation;
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+/**
+ * Data class that contains global hibernation state for a package.
+ */
+final class GlobalLevelState {
+ public String packageName;
+ public boolean hibernated;
+}
diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
new file mode 100644
index 0000000..c83659d
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
@@ -0,0 +1,162 @@
+/*
+ * 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.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.text.format.DateUtils;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Disk store utility class for hibernation states.
+ *
+ * @param <T> the type of hibernation state data
+ */
+class HibernationStateDiskStore<T> {
+ private static final String TAG = "HibernationStateDiskStore";
+
+ // Time to wait before actually writing. Saves extra writes if data changes come in batches.
+ private static final long DISK_WRITE_DELAY = 1L * DateUtils.MINUTE_IN_MILLIS;
+ private static final String STATES_FILE_NAME = "states";
+
+ private final File mHibernationFile;
+ private final ScheduledExecutorService mExecutorService;
+ private final ProtoReadWriter<List<T>> mProtoReadWriter;
+ private List<T> mScheduledStatesToWrite = new ArrayList<>();
+ private ScheduledFuture<?> mFuture;
+
+ /**
+ * Initialize a disk store for hibernation states in the given directory.
+ *
+ * @param hibernationDir directory to write/read states file
+ * @param readWriter writer/reader of states proto
+ * @param executorService scheduled executor for writing data
+ */
+ HibernationStateDiskStore(@NonNull File hibernationDir,
+ @NonNull ProtoReadWriter<List<T>> readWriter,
+ @NonNull ScheduledExecutorService executorService) {
+ this(hibernationDir, readWriter, executorService, STATES_FILE_NAME);
+ }
+
+ @VisibleForTesting
+ HibernationStateDiskStore(@NonNull File hibernationDir,
+ @NonNull ProtoReadWriter<List<T>> readWriter,
+ @NonNull ScheduledExecutorService executorService,
+ @NonNull String fileName) {
+ mHibernationFile = new File(hibernationDir, fileName);
+ mExecutorService = executorService;
+ mProtoReadWriter = readWriter;
+ }
+
+ /**
+ * Schedule a full write of all the hibernation states to the file on disk. Does not run
+ * immediately and subsequent writes override previous ones.
+ *
+ * @param hibernationStates list of hibernation states to write to disk
+ */
+ void scheduleWriteHibernationStates(@NonNull List<T> hibernationStates) {
+ synchronized (this) {
+ mScheduledStatesToWrite = hibernationStates;
+ if (mExecutorService.isShutdown()) {
+ Slog.e(TAG, "Scheduled executor service is shut down.");
+ return;
+ }
+
+ // Already have write scheduled
+ if (mFuture != null) {
+ Slog.i(TAG, "Write already scheduled. Skipping schedule.");
+ return;
+ }
+
+ mFuture = mExecutorService.schedule(this::writeHibernationStates, DISK_WRITE_DELAY,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Read hibernation states from disk.
+ *
+ * @return the parsed list of hibernation states, null if file does not exist
+ */
+ @Nullable
+ List<T> readHibernationStates() {
+ synchronized (this) {
+ if (!mHibernationFile.exists()) {
+ Slog.i(TAG, "No hibernation file on disk for file " + mHibernationFile.getPath());
+ return null;
+ }
+ AtomicFile atomicFile = new AtomicFile(mHibernationFile);
+
+ try {
+ FileInputStream inputStream = atomicFile.openRead();
+ ProtoInputStream protoInputStream = new ProtoInputStream(inputStream);
+ return mProtoReadWriter.readFromProto(protoInputStream);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read states protobuf.", e);
+ return null;
+ }
+ }
+ }
+
+ @WorkerThread
+ private void writeHibernationStates() {
+ synchronized (this) {
+ writeStateProto(mScheduledStatesToWrite);
+ mScheduledStatesToWrite.clear();
+ mFuture = null;
+ }
+ }
+
+ @WorkerThread
+ private void writeStateProto(List<T> states) {
+ AtomicFile atomicFile = new AtomicFile(mHibernationFile);
+
+ FileOutputStream fileOutputStream;
+ try {
+ fileOutputStream = atomicFile.startWrite();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to start write to states protobuf.", e);
+ return;
+ }
+
+ try {
+ ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+ mProtoReadWriter.writeToProto(protoOutputStream, states);
+ protoOutputStream.flush();
+ atomicFile.finishWrite(fileOutputStream);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to finish write to states protobuf.", e);
+ atomicFile.failWrite(fileOutputStream);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java
new file mode 100644
index 0000000..0cbc09a
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+
+/**
+ * Proto utility that reads and writes proto for some data.
+ *
+ * @param <T> data that can be written and read from a proto
+ */
+interface ProtoReadWriter<T> {
+
+ /**
+ * Write data to a proto stream
+ */
+ void writeToProto(@NonNull ProtoOutputStream stream, @NonNull T data);
+
+ /**
+ * Parse data from the proto stream and return
+ */
+ @Nullable T readFromProto(@NonNull ProtoInputStream stream) throws IOException;
+}
diff --git a/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java
new file mode 100644
index 0000000..a24c4c5
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java
@@ -0,0 +1,78 @@
+/*
+ * 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.apphibernation;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads and writes protos for {@link UserLevelState} hiberation states.
+ */
+final class UserLevelHibernationProto implements ProtoReadWriter<List<UserLevelState>> {
+ private static final String TAG = "UserLevelHibernationProtoReadWriter";
+
+ @Override
+ public void writeToProto(@NonNull ProtoOutputStream stream,
+ @NonNull List<UserLevelState> data) {
+ for (int i = 0, size = data.size(); i < size; i++) {
+ long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE);
+ UserLevelState state = data.get(i);
+ stream.write(UserLevelHibernationStateProto.PACKAGE_NAME, state.packageName);
+ stream.write(UserLevelHibernationStateProto.HIBERNATED, state.hibernated);
+ stream.end(token);
+ }
+ }
+
+ @Override
+ public @Nullable List<UserLevelState> readFromProto(@NonNull ProtoInputStream stream)
+ throws IOException {
+ List<UserLevelState> list = new ArrayList<>();
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (stream.getFieldNumber()
+ != (int) UserLevelHibernationStatesProto.HIBERNATION_STATE) {
+ continue;
+ }
+ UserLevelState state = new UserLevelState();
+ long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE);
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) UserLevelHibernationStateProto.PACKAGE_NAME:
+ state.packageName =
+ stream.readString(UserLevelHibernationStateProto.PACKAGE_NAME);
+ break;
+ case (int) UserLevelHibernationStateProto.HIBERNATED:
+ state.hibernated =
+ stream.readBoolean(UserLevelHibernationStateProto.HIBERNATED);
+ break;
+ default:
+ Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber());
+ }
+ }
+ stream.end(token);
+ list.add(state);
+ }
+ return list;
+ }
+}
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/services/core/java/com/android/server/apphibernation/UserLevelState.java
similarity index 73%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to services/core/java/com/android/server/apphibernation/UserLevelState.java
index 19b20f2..c66dad8 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/services/core/java/com/android/server/apphibernation/UserLevelState.java
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+package com.android.server.apphibernation;
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+/**
+ * Data class that contains hibernation state info of a package for a user.
+ */
+final class UserLevelState {
+ public String packageName;
+ public boolean hibernated;
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index f07da8f..10fe1e1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2796,6 +2796,8 @@
if (callback == null) {
return;
}
+ final boolean mayWatchPackageName =
+ packageName != null && !filterAppAccessUnlocked(packageName);
synchronized (this) {
int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
@@ -2824,7 +2826,7 @@
}
cbs.add(cb);
}
- if (packageName != null) {
+ if (mayWatchPackageName) {
ArraySet<ModeCallback> cbs = mPackageModeWatchers.get(packageName);
if (cbs == null) {
cbs = new ArraySet<>();
@@ -3008,13 +3010,27 @@
Objects.requireNonNull(packageName);
try {
verifyAndGetBypass(uid, packageName, null);
-
+ if (filterAppAccessUnlocked(packageName)) {
+ return AppOpsManager.MODE_ERRORED;
+ }
return AppOpsManager.MODE_ALLOWED;
} catch (SecurityException ignored) {
return AppOpsManager.MODE_ERRORED;
}
}
+ /**
+ * This method will check with PackageManager to determine if the package provided should
+ * be visible to the {@link Binder#getCallingUid()}.
+ *
+ * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+ */
+ private boolean filterAppAccessUnlocked(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ return LocalServices.getService(PackageManagerInternal.class)
+ .filterAppAccess(packageName, callingUid, UserHandle.getUserId(callingUid));
+ }
+
@Override
public int noteProxyOperation(int code, int proxiedUid, String proxiedPackageName,
String proxiedAttributionTag, int proxyUid, String proxyPackageName,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d575963..a43f0c6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1076,7 +1076,6 @@
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
synchronized (mHdmiClientLock) {
- mHdmiCecSink = false;
mHdmiManager = mContext.getSystemService(HdmiControlManager.class);
if (mHdmiManager != null) {
mHdmiManager.addHdmiControlStatusChangeListener(
@@ -1508,7 +1507,8 @@
if (isPlatformTelevision()) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null && mHdmiPlaybackClient != null) {
- updateHdmiCecSinkLocked(mHdmiCecSink | false);
+ updateHdmiCecSinkLocked(
+ mFullVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI));
}
}
}
@@ -1518,7 +1518,8 @@
if (isPlatformTelevision()) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
- updateHdmiCecSinkLocked(mHdmiCecSink | false);
+ updateHdmiCecSinkLocked(
+ mFullVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI));
}
}
}
@@ -2670,8 +2671,7 @@
if (adjustVolume) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
- // mHdmiCecSink true => mHdmiPlaybackClient != null
- if (mHdmiCecSink
+ if (mHdmiPlaybackClient != null
&& mHdmiCecVolumeControlEnabled
&& streamTypeAlias == AudioSystem.STREAM_MUSIC
// vol change on a full volume device
@@ -7825,9 +7825,8 @@
@GuardedBy("mHdmiClientLock")
private void updateHdmiCecSinkLocked(boolean hdmiCecSink) {
- mHdmiCecSink = hdmiCecSink;
if (!hasDeviceVolumeBehavior(AudioSystem.DEVICE_OUT_HDMI)) {
- if (mHdmiCecSink) {
+ if (hdmiCecSink) {
if (DEBUG_VOL) {
Log.d(TAG, "CEC sink: setting HDMI as full vol device");
}
@@ -7885,9 +7884,6 @@
// Set only when device is a set-top box.
@GuardedBy("mHdmiClientLock")
private HdmiPlaybackClient mHdmiPlaybackClient;
- // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
- @GuardedBy("mHdmiClientLock")
- private boolean mHdmiCecSink;
// Set only when device is an audio system.
@GuardedBy("mHdmiClientLock")
private HdmiAudioSystemClient mHdmiAudioSystemClient;
@@ -8142,7 +8138,6 @@
pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
pw.print(" mFullVolumeDevices="); pw.println(dumpDeviceTypes(mFullVolumeDevices));
pw.print(" mExtVolumeController="); pw.println(mExtVolumeController);
- pw.print(" mHdmiCecSink="); pw.println(mHdmiCecSink);
pw.print(" mHdmiAudioSystemClient="); pw.println(mHdmiAudioSystemClient);
pw.print(" mHdmiPlaybackClient="); pw.println(mHdmiPlaybackClient);
pw.print(" mHdmiTvClient="); pw.println(mHdmiTvClient);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index d3b5b5a..303c080 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -114,11 +114,8 @@
* Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
* be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
*/
- default void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
- @NonNull IInvalidationCallback callback) {
- throw new IllegalStateException("Providers that support invalidation must override"
- + " this method");
- }
+ void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
+ @NonNull IInvalidationCallback callback);
long getAuthenticatorId(int sensorId, int userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index a4a8401..1135126 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -28,6 +28,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
@@ -112,6 +113,7 @@
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
private int mCurrentUserId = UserHandle.USER_NULL;
+ private boolean mIsUdfps = false;
private final int mSensorId;
private final class BiometricTaskStackListener extends TaskStackListener {
@@ -144,11 +146,11 @@
private final LockoutFrameworkImpl.LockoutResetCallback mLockoutResetCallback =
new LockoutFrameworkImpl.LockoutResetCallback() {
- @Override
- public void onLockoutReset(int userId) {
- mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
- }
- };
+ @Override
+ public void onLockoutReset(int userId) {
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
+ }
+ };
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
@@ -334,28 +336,28 @@
}
final IBiometricsFingerprint daemon = getDaemon();
- boolean isUdfps = false;
+ mIsUdfps = false;
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
daemon);
if (extension != null) {
try {
- isUdfps = extension.isUdfps(sensorId);
+ mIsUdfps = extension.isUdfps(sensorId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception while quering udfps", e);
- isUdfps = false;
+ mIsUdfps = false;
}
}
final @FingerprintSensorProperties.SensorType int sensorType =
- isUdfps ? FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
- : FingerprintSensorProperties.TYPE_REAR;
+ mIsUdfps ? FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+ : FingerprintSensorProperties.TYPE_REAR;
// resetLockout is controlled by the framework, so hardwareAuthToken is not required
final boolean resetLockoutRequiresHardwareAuthToken = false;
final int maxEnrollmentsPerUser = mContext.getResources()
.getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
- mSensorProperties = new FingerprintSensorPropertiesInternal(sensorId,
+ mSensorProperties = new FingerprintSensorPropertiesInternal(context, sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
sensorType, resetLockoutRequiresHardwareAuthToken);
}
@@ -413,7 +415,7 @@
Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+ mScheduler.getCurrentClient());
try {
- mDaemon = IBiometricsFingerprint.getService();
+ mDaemon = IBiometricsFingerprint.getService(true /* retry */);
} catch (java.util.NoSuchElementException e) {
// Service doesn't exist or cannot be opened.
Slog.w(TAG, "NoSuchElementException", e);
@@ -630,13 +632,13 @@
@NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
@NonNull String opPackageName) {
mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
+ scheduleUpdateActiveUserWithoutHandler(userId);
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
- userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
+ userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
+ mSensorProperties.sensorId, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client);
});
}
@@ -780,6 +782,17 @@
}
@Override
+ public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
+ @NonNull IInvalidationCallback callback) {
+ // TODO (b/179101888): Remove this temporary workaround.
+ try {
+ callback.onCompleted();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to complete InvalidateAuthenticatorId");
+ }
+ }
+
+ @Override
public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
PerformanceTracker performanceTracker =
PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
@@ -787,6 +800,7 @@
JSONObject dump = new JSONObject();
try {
dump.put("service", TAG);
+ dump.put("isUdfps", mIsUdfps);
JSONArray sets = new JSONArray();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b282484..1a4f20c7 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -57,6 +57,7 @@
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -1025,6 +1026,8 @@
+ (networkAgentConfig.acceptUnvalidated ? " acceptUnvalidated" : "")
+ (networkAgentConfig.acceptPartialConnectivity ? " acceptPartialConnectivity" : "")
+ (clatd.isStarted() ? " clat{" + clatd + "} " : "")
+ + (declaredUnderlyingNetworks != null
+ ? " underlying{" + Arrays.toString(declaredUnderlyingNetworks) + "}" : "")
+ " lp{" + linkProperties + "}"
+ " nc{" + networkCapabilities + "}"
+ "}";
diff --git a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java b/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
index 5dc8c1a..aadaf4d 100644
--- a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
+++ b/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
@@ -16,7 +16,6 @@
package com.android.server.connectivity;
-import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -72,6 +71,10 @@
private static final int DELAY_LONG = 4;
private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
+ // Return values for #setCurrentProxyScriptUrl
+ public static final boolean DONT_SEND_BROADCAST = false;
+ public static final boolean DO_SEND_BROADCAST = true;
+
private String mCurrentPac;
@GuardedBy("mProxyLock")
private volatile Uri mPacUrl = Uri.EMPTY;
@@ -90,7 +93,7 @@
private volatile boolean mHasSentBroadcast;
private volatile boolean mHasDownloaded;
- private final Handler mConnectivityHandler;
+ private Handler mConnectivityHandler;
private final int mProxyMessage;
/**
@@ -99,13 +102,6 @@
private final Object mProxyLock = new Object();
/**
- * Lock ensuring consistency between the values of mHasSentBroadcast, mHasDownloaded, the
- * last URL and port, and the broadcast message being sent with the correct arguments.
- * TODO : this should probably protect all instances of these variables
- */
- private final Object mBroadcastStateLock = new Object();
-
- /**
* Runnable to download PAC script.
* The behavior relies on the assumption it always runs on mNetThread to guarantee that the
* latest data fetched from mPacUrl is stored in mProxyService.
@@ -150,7 +146,7 @@
}
}
- public PacProxyInstaller(@NonNull Context context, @NonNull Handler handler, int proxyMessage) {
+ public PacProxyInstaller(Context context, Handler handler, int proxyMessage) {
mContext = context;
mLastPort = -1;
final HandlerThread netThread = new HandlerThread("android.pacproxyinstaller",
@@ -180,27 +176,31 @@
* PacProxyInstaller will trigger a new broadcast when it is ready.
*
* @param proxy Proxy information that is about to be broadcast.
+ * @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST
*/
- public void setCurrentProxyScriptUrl(@NonNull ProxyInfo proxy) {
- synchronized (mBroadcastStateLock) {
- if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
- if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) return;
- mPacUrl = proxy.getPacFileUrl();
- mCurrentDelay = DELAY_1;
- mHasSentBroadcast = false;
- mHasDownloaded = false;
- getAlarmManager().cancel(mPacRefreshIntent);
- bind();
- } else {
- getAlarmManager().cancel(mPacRefreshIntent);
- synchronized (mProxyLock) {
- mPacUrl = Uri.EMPTY;
- mCurrentPac = null;
- if (mProxyService != null) {
- unbind();
- }
+ public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
+ if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
+ if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
+ // Allow to send broadcast, nothing to do.
+ return DO_SEND_BROADCAST;
+ }
+ mPacUrl = proxy.getPacFileUrl();
+ mCurrentDelay = DELAY_1;
+ mHasSentBroadcast = false;
+ mHasDownloaded = false;
+ getAlarmManager().cancel(mPacRefreshIntent);
+ bind();
+ return DONT_SEND_BROADCAST;
+ } else {
+ getAlarmManager().cancel(mPacRefreshIntent);
+ synchronized (mProxyLock) {
+ mPacUrl = Uri.EMPTY;
+ mCurrentPac = null;
+ if (mProxyService != null) {
+ unbind();
}
}
+ return DO_SEND_BROADCAST;
}
}
@@ -275,7 +275,6 @@
getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent);
}
- @GuardedBy("mProxyLock")
private void setCurrentProxyScript(String script) {
if (mProxyService == null) {
Log.e(TAG, "setCurrentProxyScript: no proxy service");
@@ -348,9 +347,6 @@
public void setProxyPort(int port) {
if (mLastPort != -1) {
// Always need to send if port changed
- // TODO: Here lacks synchronization because this write cannot
- // guarantee that it's visible from sendProxyIfNeeded() when
- // it's called by a Runnable which is post by mNetThread.
mHasSentBroadcast = false;
}
mLastPort = port;
@@ -390,15 +386,13 @@
mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
}
- private void sendProxyIfNeeded() {
- synchronized (mBroadcastStateLock) {
- if (!mHasDownloaded || (mLastPort == -1)) {
- return;
- }
- if (!mHasSentBroadcast) {
- sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
- mHasSentBroadcast = true;
- }
+ private synchronized void sendProxyIfNeeded() {
+ if (!mHasDownloaded || (mLastPort == -1)) {
+ return;
+ }
+ if (!mHasSentBroadcast) {
+ sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
+ mHasSentBroadcast = true;
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index b618d2b..d83ff83 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -226,9 +226,9 @@
final ProxyInfo defaultProxy = getDefaultProxy();
final ProxyInfo proxyInfo = null != defaultProxy ?
defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList());
- mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo);
- if (!shouldSendBroadcast(proxyInfo)) {
+ if (mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo)
+ == PacProxyInstaller.DONT_SEND_BROADCAST) {
return;
}
if (DBG) Log.d(TAG, "sending Proxy Broadcast for " + proxyInfo);
@@ -244,13 +244,6 @@
}
}
- private boolean shouldSendBroadcast(ProxyInfo proxy) {
- if (Uri.EMPTY.equals(proxy.getPacFileUrl())) return false;
- if (proxy.getPacFileUrl().equals(proxy.getPacFileUrl())
- && (proxy.getPort() > 0)) return true;
- return true;
- }
-
/**
* Sets the global proxy in memory. Also writes the values to the global settings of the device.
*
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index b455a3f..d956ba3 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1982,27 +1982,28 @@
* secondary thread to perform connection work, returning quickly.
*
* Should only be called to respond to Binder requests as this enforces caller permission. Use
- * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, LinkProperties)} to skip the
+ * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, Network, LinkProperties)} to skip the
* permission check only when the caller is trusted (or the call is initiated by the system).
*/
- public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
+ public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying,
+ LinkProperties egress) {
enforceControlPermission();
final long token = Binder.clearCallingIdentity();
try {
- startLegacyVpnPrivileged(profile, keyStore, egress);
+ startLegacyVpnPrivileged(profile, keyStore, underlying, egress);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
- * Like {@link #startLegacyVpn(VpnProfile, KeyStore, LinkProperties)}, but does not check
- * permissions under the assumption that the caller is the system.
+ * Like {@link #startLegacyVpn(VpnProfile, KeyStore, Network, LinkProperties)}, but does not
+ * check permissions under the assumption that the caller is the system.
*
* Callers are responsible for checking permissions if needed.
*/
public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore,
- LinkProperties egress) {
+ @Nullable Network underlying, @NonNull LinkProperties egress) {
UserManager mgr = UserManager.get(mContext);
UserInfo user = mgr.getUserInfo(mUserId);
if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
@@ -2128,6 +2129,9 @@
config.session = profile.name;
config.isMetered = false;
config.proxyInfo = profile.proxy;
+ if (underlying != null) {
+ config.underlyingNetworks = new Network[] { underlying };
+ }
config.addLegacyRoutes(profile.routes);
if (!profile.dnsServers.isEmpty()) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
new file mode 100644
index 0000000..802472f
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * A state of the device defined by the {@link DeviceStateProvider} and managed by the
+ * {@link DeviceStateManagerService}.
+ * <p>
+ * Device state is an abstract concept that allows mapping the current state of the device to the
+ * state of the system. This is useful for variable-state devices, like foldable or rollable
+ * devices, that can be configured by users into differing hardware states, which each may have a
+ * different expected use case.
+ *
+ * @see DeviceStateProvider
+ * @see DeviceStateManagerService
+ */
+public final class DeviceState {
+ /** Unique identifier for the device state. */
+ @IntRange(from = INVALID_DEVICE_STATE)
+ private final int mIdentifier;
+
+ /** String description of the device state. */
+ @NonNull
+ private final String mName;
+
+ public DeviceState(@IntRange(from = INVALID_DEVICE_STATE) int identifier,
+ @NonNull String name) {
+ if (identifier != INVALID_DEVICE_STATE && identifier < 0) {
+ throw new IllegalArgumentException("Identifier must be greater than or equal to zero.");
+ }
+ mIdentifier = identifier;
+ mName = name;
+ }
+
+ /** Returns the unique identifier for the device state. */
+ @IntRange(from = INVALID_DEVICE_STATE)
+ public int getIdentifier() {
+ return mIdentifier;
+ }
+
+ /** Returns a string description of the device state. */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DeviceState that = (DeviceState) o;
+ return mIdentifier == that.mIdentifier
+ && Objects.equals(mName, that.mName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIdentifier, mName);
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index d7dcbde..375ec3a 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,7 +19,9 @@
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.devicestate.IDeviceStateManager;
@@ -29,7 +31,6 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -42,7 +43,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Optional;
/**
* A system service that manages the state of a device with user-configurable hardware like a
@@ -74,26 +75,31 @@
@NonNull
private final BinderService mBinderService;
+ // All supported device states keyed by identifier.
@GuardedBy("mLock")
- private IntArray mSupportedDeviceStates;
+ private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
- // The current committed device state.
+ // The current committed device state. The default of INVALID_DEVICE_STATE will be replaced by
+ // the current state after the initial callback from the DeviceStateProvider.
@GuardedBy("mLock")
- private int mCommittedState = INVALID_DEVICE_STATE;
+ @NonNull
+ private DeviceState mCommittedState = new DeviceState(INVALID_DEVICE_STATE, "INVALID");
// The device state that is currently awaiting callback from the policy to be committed.
@GuardedBy("mLock")
- private int mPendingState = INVALID_DEVICE_STATE;
+ @NonNull
+ private Optional<DeviceState> mPendingState = Optional.empty();
// Whether or not the policy is currently waiting to be notified of the current pending state.
@GuardedBy("mLock")
private boolean mIsPolicyWaitingForState = false;
// The device state that is currently requested and is next to be configured and committed.
// Can be overwritten by an override state value if requested.
@GuardedBy("mLock")
- private int mRequestedState = INVALID_DEVICE_STATE;
- // The most recently requested override state, or INVALID_DEVICE_STATE if no override is
- // requested.
+ @NonNull
+ private Optional<DeviceState> mRequestedState = Optional.empty();
+ // The most recently requested override state, or empty if no override is requested.
@GuardedBy("mLock")
- private int mRequestedOverrideState = INVALID_DEVICE_STATE;
+ @NonNull
+ private Optional<DeviceState> mRequestedOverrideState = Optional.empty();
// List of registered callbacks indexed by process id.
@GuardedBy("mLock")
@@ -122,18 +128,20 @@
*
* @see #getPendingState()
*/
- int getCommittedState() {
+ @NonNull
+ DeviceState getCommittedState() {
synchronized (mLock) {
return mCommittedState;
}
}
/**
- * Returns the state the system is currently configuring, or {@link #INVALID_DEVICE_STATE} if
- * the system is not in the process of configuring a state.
+ * Returns the state the system is currently configuring, or {@link Optional#empty()} if the
+ * system is not in the process of configuring a state.
*/
@VisibleForTesting
- int getPendingState() {
+ @NonNull
+ Optional<DeviceState> getPendingState() {
synchronized (mLock) {
return mPendingState;
}
@@ -143,7 +151,8 @@
* Returns the requested state. The service will configure the device to match the requested
* state when possible.
*/
- int getRequestedState() {
+ @NonNull
+ Optional<DeviceState> getRequestedState() {
synchronized (mLock) {
return mRequestedState;
}
@@ -165,7 +174,7 @@
return false;
}
- mRequestedOverrideState = overrideState;
+ mRequestedOverrideState = getStateLocked(overrideState);
updatePendingStateLocked();
}
@@ -181,20 +190,24 @@
}
/**
- * Returns the current requested override state, or {@link #INVALID_DEVICE_STATE} is no override
+ * Returns the current requested override state, or {@link Optional#empty()} if no override
* state is requested.
*/
- int getOverrideState() {
+ @NonNull
+ Optional<DeviceState> getOverrideState() {
synchronized (mLock) {
return mRequestedOverrideState;
}
}
/** Returns the list of currently supported device states. */
- int[] getSupportedStates() {
+ DeviceState[] getSupportedStates() {
synchronized (mLock) {
- // Copy array to prevent external modification of internal state.
- return Arrays.copyOf(mSupportedDeviceStates.toArray(), mSupportedDeviceStates.size());
+ DeviceState[] supportedStates = new DeviceState[mDeviceStates.size()];
+ for (int i = 0; i < supportedStates.length; i++) {
+ supportedStates[i] = mDeviceStates.valueAt(i);
+ }
+ return supportedStates;
}
}
@@ -203,24 +216,26 @@
return mBinderService;
}
- private void updateSupportedStates(int[] supportedDeviceStates) {
- // Must ensure sorted as isSupportedStateLocked() impl uses binary search.
- Arrays.sort(supportedDeviceStates, 0, supportedDeviceStates.length);
+ private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
synchronized (mLock) {
- mSupportedDeviceStates = IntArray.wrap(supportedDeviceStates);
+ mDeviceStates.clear();
+ for (int i = 0; i < supportedDeviceStates.length; i++) {
+ DeviceState state = supportedDeviceStates[i];
+ mDeviceStates.put(state.getIdentifier(), state);
+ }
- if (mRequestedState != INVALID_DEVICE_STATE
- && !isSupportedStateLocked(mRequestedState)) {
+ if (mRequestedState.isPresent()
+ && !isSupportedStateLocked(mRequestedState.get().getIdentifier())) {
// The current requested state is no longer valid. We'll clear it here, though
// we won't actually update the current state until a callback comes from the
// provider with the most recent state.
- mRequestedState = INVALID_DEVICE_STATE;
+ mRequestedState = Optional.empty();
}
- if (mRequestedOverrideState != INVALID_DEVICE_STATE
- && !isSupportedStateLocked(mRequestedOverrideState)) {
+ if (mRequestedOverrideState.isPresent()
+ && !isSupportedStateLocked(mRequestedOverrideState.get().getIdentifier())) {
// The current override state is no longer valid. We'll clear it here and update
// the committed state if necessary.
- mRequestedOverrideState = INVALID_DEVICE_STATE;
+ mRequestedOverrideState = Optional.empty();
}
updatePendingStateLocked();
}
@@ -230,10 +245,19 @@
/**
* Returns {@code true} if the provided state is supported. Requires that
- * {@link #mSupportedDeviceStates} is sorted prior to calling.
+ * {@link #mDeviceStates} is sorted prior to calling.
*/
- private boolean isSupportedStateLocked(int state) {
- return mSupportedDeviceStates.binarySearch(state) >= 0;
+ private boolean isSupportedStateLocked(int identifier) {
+ return mDeviceStates.contains(identifier);
+ }
+
+ /**
+ * Returns the {@link DeviceState} with the supplied {@code identifier}, or {@code null} if
+ * there is no device state with the identifier.
+ */
+ @Nullable
+ private Optional<DeviceState> getStateLocked(int identifier) {
+ return Optional.ofNullable(mDeviceStates.get(identifier));
}
/**
@@ -242,10 +266,11 @@
*
* @see #isSupportedStateLocked(int)
*/
- private void requestState(int state) {
+ private void requestState(int identifier) {
synchronized (mLock) {
- if (isSupportedStateLocked(state)) {
- mRequestedState = state;
+ final Optional<DeviceState> requestedState = getStateLocked(identifier);
+ if (requestedState.isPresent()) {
+ mRequestedState = requestedState;
}
updatePendingStateLocked();
}
@@ -259,19 +284,19 @@
* changed.
*/
private void updatePendingStateLocked() {
- if (mPendingState != INVALID_DEVICE_STATE) {
+ if (mPendingState.isPresent()) {
// Have pending state, can not configure a new state until the state is committed.
return;
}
- int stateToConfigure;
- if (mRequestedOverrideState != INVALID_DEVICE_STATE) {
- stateToConfigure = mRequestedOverrideState;
+ final DeviceState stateToConfigure;
+ if (mRequestedOverrideState.isPresent()) {
+ stateToConfigure = mRequestedOverrideState.get();
} else {
- stateToConfigure = mRequestedState;
+ stateToConfigure = mRequestedState.orElse(null);
}
- if (stateToConfigure == INVALID_DEVICE_STATE) {
+ if (stateToConfigure == null) {
// No currently requested state.
return;
}
@@ -281,7 +306,7 @@
return;
}
- mPendingState = stateToConfigure;
+ mPendingState = Optional.of(stateToConfigure);
mIsPolicyWaitingForState = true;
}
@@ -302,7 +327,7 @@
return;
}
mIsPolicyWaitingForState = false;
- state = mPendingState;
+ state = mPendingState.get().getIdentifier();
}
if (DEBUG) {
@@ -333,9 +358,9 @@
if (DEBUG) {
Slog.d(TAG, "Committing state: " + mPendingState);
}
- mCommittedState = mPendingState;
- newState = mCommittedState;
- mPendingState = INVALID_DEVICE_STATE;
+ mCommittedState = mPendingState.get();
+ newState = mCommittedState.getIdentifier();
+ mPendingState = Optional.empty();
updatePendingStateLocked();
}
@@ -389,7 +414,7 @@
}
mCallbacks.put(callingPid, record);
- currentState = mCommittedState;
+ currentState = mCommittedState.getIdentifier();
}
// Notify the callback of the state at registration.
@@ -406,10 +431,10 @@
pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
synchronized (mLock) {
- pw.println(" mCommittedState=" + toString(mCommittedState));
- pw.println(" mPendingState=" + toString(mPendingState));
- pw.println(" mRequestedState=" + toString(mRequestedState));
- pw.println(" mRequestedOverrideState=" + toString(mRequestedOverrideState));
+ pw.println(" mCommittedState=" + mCommittedState);
+ pw.println(" mPendingState=" + mPendingState);
+ pw.println(" mRequestedState=" + mRequestedState);
+ pw.println(" mRequestedOverrideState=" + mRequestedOverrideState);
final int callbackCount = mCallbacks.size();
pw.println();
@@ -421,30 +446,28 @@
}
}
- private String toString(int state) {
- return state == INVALID_DEVICE_STATE ? "(none)" : String.valueOf(state);
- }
-
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@Override
- public void onSupportedDeviceStatesChanged(int[] newDeviceStates) {
+ public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
+ if (newDeviceStates.length == 0) {
+ throw new IllegalArgumentException("Supported device states must not be empty");
+ }
for (int i = 0; i < newDeviceStates.length; i++) {
- if (newDeviceStates[i] < 0) {
- throw new IllegalArgumentException("Supported device states includes invalid"
- + " value: " + newDeviceStates[i]);
+ if (newDeviceStates[i].getIdentifier() == INVALID_DEVICE_STATE) {
+ throw new IllegalArgumentException(
+ "Supported device states includes INVALID_DEVICE_STATE identifier");
}
}
-
updateSupportedStates(newDeviceStates);
}
@Override
- public void onStateChanged(int state) {
- if (state < 0) {
- throw new IllegalArgumentException("Invalid state: " + state);
+ public void onStateChanged(@IntRange(from = 0) int identifier) {
+ if (identifier < 0) {
+ throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
- requestState(state);
+ requestState(identifier);
}
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index cf3b297..7914531 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -16,11 +16,10 @@
package com.android.server.devicestate;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
-
import android.os.ShellCommand;
import java.io.PrintWriter;
+import java.util.Optional;
/**
* ShellCommands for {@link DeviceStateManagerService}.
@@ -52,24 +51,15 @@
}
private void printState(PrintWriter pw) {
- int committedState = mInternal.getCommittedState();
- int requestedState = mInternal.getRequestedState();
- int requestedOverrideState = mInternal.getOverrideState();
+ DeviceState committedState = mInternal.getCommittedState();
+ Optional<DeviceState> requestedState = mInternal.getRequestedState();
+ Optional<DeviceState> requestedOverrideState = mInternal.getOverrideState();
- if (committedState == INVALID_DEVICE_STATE) {
- pw.println("Device state: (invalid)");
- } else {
- pw.println("Device state: " + committedState);
- }
-
- if (requestedOverrideState != INVALID_DEVICE_STATE) {
+ pw.println("Committed state: " + committedState);
+ if (requestedOverrideState.isPresent()) {
pw.println("----------------------");
- if (requestedState == INVALID_DEVICE_STATE) {
- pw.println("Base state: (invalid)");
- } else {
- pw.println("Base state: " + requestedState);
- }
- pw.println("Override state: " + committedState);
+ pw.println("Base state: " + requestedState.orElse(null));
+ pw.println("Override state: " + requestedOverrideState.get());
}
}
@@ -102,15 +92,12 @@
}
private int runPrintStates(PrintWriter pw) {
- int[] states = mInternal.getSupportedStates();
- pw.print("Supported states: [ ");
+ DeviceState[] states = mInternal.getSupportedStates();
+ pw.print("Supported states: [\n");
for (int i = 0; i < states.length; i++) {
- pw.print(states[i]);
- if (i < states.length - 1) {
- pw.print(", ");
- }
+ pw.print(" " + states[i] + ",\n");
}
- pw.println(" ]");
+ pw.println("]");
return 0;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 0e8bf9b..2d4377f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -16,9 +16,11 @@
package com.android.server.devicestate;
+import android.annotation.IntRange;
+
/**
- * Responsible for providing the set of currently supported device states and well as the current
- * device state.
+ * Responsible for providing the set of supported {@link DeviceState device states} as well as the
+ * current device state.
*
* @see DeviceStatePolicy
*/
@@ -26,8 +28,8 @@
/**
* Registers a listener for changes in provider state.
* <p>
- * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(int[])} be called
- * followed by {@link Listener#onStateChanged(int)} with the initial values on successful
+ * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(DeviceState[])} be
+ * called followed by {@link Listener#onStateChanged(int)} with the initial values on successful
* registration of the listener.
*/
void setListener(Listener listener);
@@ -35,35 +37,36 @@
/** Callback for changes in {@link DeviceStateProvider} state. */
interface Listener {
/**
- * Called to notify the listener of a change in supported device states. Required to be
- * called once on successful registration of the listener and then once on every
- * subsequent change in supported device states.
+ * Called to notify the listener of a change in supported {@link DeviceState device states}.
+ * Required to be called once on successful registration of the listener and then once on
+ * every subsequent change in supported device states.
* <p>
* The set of device states can change based on the current hardware state of the device.
* For example, if a device state depends on a particular peripheral device (display, etc)
* it would only be reported as supported when the device is plugged. Otherwise, it should
* not be included in the set of supported states.
* <p>
- * All values provided must be greater than or equal to zero and there must always be at
- * least one supported device state.
+ * The identifier for every provided device state must be unique and greater than or equal
+ * to zero and there must always be at least one supported device state.
*
* @param newDeviceStates array of supported device states.
*
* @throws IllegalArgumentException if the list of device states is empty or if one of the
- * provided states is less than 0.
+ * provided states contains an invalid identifier.
*/
- void onSupportedDeviceStatesChanged(int[] newDeviceStates);
+ void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates);
/**
* Called to notify the listener of a change in current device state. Required to be called
* once on successful registration of the listener and then once on every subsequent change
* in device state. Value must have been included in the set of supported device states
- * provided in the most recent call to {@link #onSupportedDeviceStatesChanged(int[])}.
+ * provided in the most recent call to
+ * {@link #onSupportedDeviceStatesChanged(DeviceState[])}.
*
- * @param state the new device state.
+ * @param identifier the identifier of the new device state.
*
* @throws IllegalArgumentException if the state is less than 0.
*/
- void onStateChanged(int state);
+ void onStateChanged(@IntRange(from = 0) int identifier);
}
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index b8e579d..4832e46 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -300,6 +300,8 @@
/** @return The default brightness configuration. */
public abstract BrightnessConfiguration getDefaultConfig();
+ /** Recalculates the backlight-to-nits and nits-to-backlight splines. */
+ public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment);
/**
* Returns the timeout for the short term model
@@ -658,6 +660,11 @@
}
@Override
+ public void recalculateSplines(boolean applyAdjustment, float[] adjustment) {
+ // Do nothing.
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.println("SimpleMappingStrategy");
pw.println(" mSpline=" + mSpline);
@@ -696,7 +703,7 @@
// A spline mapping from nits to the corresponding backlight value, normalized to the range
// [0, 1.0].
- private final Spline mNitsToBacklightSpline;
+ private Spline mNitsToBacklightSpline;
// The default brightness configuration.
private final BrightnessConfiguration mDefaultConfig;
@@ -705,6 +712,11 @@
// a brightness in nits.
private Spline mBacklightToNitsSpline;
+ private float[] mNits;
+ private int[] mBacklight;
+
+ private boolean mBrightnessRangeAdjustmentApplied;
+
private float mMaxGamma;
private float mAutoBrightnessAdjustment;
private float mUserLux;
@@ -726,15 +738,9 @@
mUserLux = -1;
mUserBrightness = -1;
- // Setup the backlight spline
- final int N = nits.length;
- float[] normalizedBacklight = new float[N];
- for (int i = 0; i < N; i++) {
- normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
- }
-
- mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
- mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
+ mNits = nits;
+ mBacklight = backlight;
+ computeNitsBrightnessSplines(mNits);
mDefaultConfig = config;
if (mLoggingEnabled) {
@@ -868,6 +874,12 @@
}
@Override
+ public void recalculateSplines(boolean applyAdjustment, float[] adjustedNits) {
+ mBrightnessRangeAdjustmentApplied = applyAdjustment;
+ computeNitsBrightnessSplines(mBrightnessRangeAdjustmentApplied ? adjustedNits : mNits);
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.println("PhysicalMappingStrategy");
pw.println(" mConfig=" + mConfig);
@@ -878,6 +890,18 @@
pw.println(" mUserLux=" + mUserLux);
pw.println(" mUserBrightness=" + mUserBrightness);
pw.println(" mDefaultConfig=" + mDefaultConfig);
+ pw.println(" mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
+ }
+
+ private void computeNitsBrightnessSplines(float[] nits) {
+ final int len = nits.length;
+ float[] normalizedBacklight = new float[len];
+ for (int i = 0; i < len; i++) {
+ normalizedBacklight[i] = normalizeAbsoluteBrightness(mBacklight[i]);
+ }
+
+ mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
+ mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
}
private void computeSpline() {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 2375f74..a186e33 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -63,7 +63,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices;
@@ -71,7 +70,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
@@ -80,7 +78,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -118,6 +115,9 @@
private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
private static final String ATTR_NIGHT_MODE = "nightMode";
private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature";
+ private static final String ATTR_REDUCE_BRIGHT_COLORS = "reduceBrightColors";
+ private static final String ATTR_REDUCE_BRIGHT_COLORS_STRENGTH = "reduceBrightColorsStrength";
+ private static final String ATTR_REDUCE_BRIGHT_COLORS_OFFSET = "reduceBrightColorsOffset";
private static final String ATTR_LAST_NITS = "lastNits";
private static final String ATTR_DEFAULT_CONFIG = "defaultConfig";
private static final String ATTR_POWER_SAVE = "powerSaveFactor";
@@ -398,6 +398,10 @@
builder.setNightMode(mInjector.isNightDisplayActivated(mContext));
builder.setColorTemperature(mInjector.getNightDisplayColorTemperature(mContext));
+ builder.setReduceBrightColors(mInjector.isReduceBrightColorsActivated(mContext));
+ builder.setReduceBrightColorsStrength(mInjector.getReduceBrightColorsStrength(mContext));
+ builder.setReduceBrightColorsOffset(mInjector.getReduceBrightColorsOffsetFactor(mContext)
+ * brightness);
if (mColorSamplingEnabled) {
DisplayedContentSample sample = mInjector.sampleColor(mNoFramesToSample);
@@ -563,6 +567,12 @@
out.attributeBoolean(null, ATTR_NIGHT_MODE, toWrite[i].nightMode);
out.attributeInt(null, ATTR_COLOR_TEMPERATURE,
toWrite[i].colorTemperature);
+ out.attributeBoolean(null, ATTR_REDUCE_BRIGHT_COLORS,
+ toWrite[i].reduceBrightColors);
+ out.attributeInt(null, ATTR_REDUCE_BRIGHT_COLORS_STRENGTH,
+ toWrite[i].reduceBrightColorsStrength);
+ out.attributeFloat(null, ATTR_REDUCE_BRIGHT_COLORS_OFFSET,
+ toWrite[i].reduceBrightColorsOffset);
out.attributeFloat(null, ATTR_LAST_NITS,
toWrite[i].lastBrightness);
out.attributeBoolean(null, ATTR_DEFAULT_CONFIG,
@@ -641,6 +651,12 @@
builder.setNightMode(parser.getAttributeBoolean(null, ATTR_NIGHT_MODE));
builder.setColorTemperature(
parser.getAttributeInt(null, ATTR_COLOR_TEMPERATURE));
+ builder.setReduceBrightColors(
+ parser.getAttributeBoolean(null, ATTR_REDUCE_BRIGHT_COLORS));
+ builder.setReduceBrightColorsStrength(
+ parser.getAttributeInt(null, ATTR_REDUCE_BRIGHT_COLORS_STRENGTH));
+ builder.setReduceBrightColorsOffset(
+ parser.getAttributeFloat(null, ATTR_REDUCE_BRIGHT_COLORS_OFFSET));
builder.setLastBrightness(parser.getAttributeFloat(null, ATTR_LAST_NITS));
String luxValue = parser.getAttributeValue(null, ATTR_LUX);
@@ -1114,6 +1130,21 @@
return context.getSystemService(ColorDisplayManager.class).isNightDisplayActivated();
}
+ public int getReduceBrightColorsStrength(Context context) {
+ return context.getSystemService(ColorDisplayManager.class)
+ .getReduceBrightColorsStrength();
+ }
+
+ public float getReduceBrightColorsOffsetFactor(Context context) {
+ return context.getSystemService(ColorDisplayManager.class)
+ .getReduceBrightColorsOffsetFactor();
+ }
+
+ public boolean isReduceBrightColorsActivated(Context context) {
+ return context.getSystemService(ColorDisplayManager.class)
+ .isReduceBrightColorsActivated();
+ }
+
public DisplayedContentSample sampleColor(int noFramesToSample) {
final DisplayManagerInternal displayManagerInternal =
LocalServices.getService(DisplayManagerInternal.class);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 2641ee7..bf16a6d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -23,6 +23,7 @@
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
+import android.view.RoundedCorners;
import android.view.Surface;
import com.android.internal.BrightnessSynchronizer;
@@ -281,6 +282,11 @@
public DisplayCutout displayCutout;
/**
+ * The {@link RoundedCorners} if present or {@code null} otherwise.
+ */
+ public RoundedCorners roundedCorners;
+
+ /**
* The touch attachment, per {@link DisplayViewport#touch}.
*/
public int touch;
@@ -401,7 +407,8 @@
|| !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
|| !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
|| !BrightnessSynchronizer.floatEquals(brightnessDefault,
- other.brightnessDefault)) {
+ other.brightnessDefault)
+ || !Objects.equals(roundedCorners, other.roundedCorners)) {
diff |= DIFF_OTHER;
}
return diff;
@@ -444,6 +451,7 @@
brightnessMinimum = other.brightnessMinimum;
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
+ roundedCorners = other.roundedCorners;
}
// For debugging purposes
@@ -487,6 +495,9 @@
sb.append(", brightnessMinimum ").append(brightnessMinimum);
sb.append(", brightnessMaximum ").append(brightnessMaximum);
sb.append(", brightnessDefault ").append(brightnessDefault);
+ if (roundedCorners != null) {
+ sb.append(", roundedCorners ").append(roundedCorners);
+ }
sb.append(flagsToString(flags));
sb.append("}");
return sb.toString();
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index a11a745..663883a 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -21,7 +21,8 @@
/**
* Represents a collection of {@link LogicalDisplay}s which act in unison for certain behaviors and
- * operations.
+ * operations; particularly display-state.
+ *
* @hide
*/
public class DisplayGroup {
@@ -33,17 +34,44 @@
mGroupId = groupId;
}
+ /** Returns the identifier for the Group. */
int getGroupId() {
return mGroupId;
}
- void addDisplay(LogicalDisplay display) {
+ /**
+ * Adds the provided {@code display} to the Group
+ *
+ * @param display the {@link LogicalDisplay} to add to the Group
+ */
+ void addDisplayLocked(LogicalDisplay display) {
if (!mDisplays.contains(display)) {
mDisplays.add(display);
}
}
- boolean removeDisplay(LogicalDisplay display) {
+ /**
+ * Removes the provided {@code display} from the Group.
+ *
+ * @param display The {@link LogicalDisplay} to remove from the Group.
+ * @return {@code true} if the {@code display} was removed; otherwise {@code false}
+ */
+ boolean removeDisplayLocked(LogicalDisplay display) {
return mDisplays.remove(display);
}
+
+ /** Returns {@code true} if there are no {@link LogicalDisplay LogicalDisplays} in the Group. */
+ boolean isEmptyLocked() {
+ return mDisplays.isEmpty();
+ }
+
+ /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */
+ int getSizeLocked() {
+ return mDisplays.size();
+ }
+
+ /** Returns the ID of the {@link LogicalDisplay} at the provided {@code index}. */
+ int getIdLocked(int index) {
+ return mDisplays.get(index).getDisplayIdLocked();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c3f8d8c..e5151d8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -57,6 +57,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
@@ -180,8 +181,8 @@
private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable";
private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
-
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
+ private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
@@ -190,6 +191,7 @@
private static final int MSG_UPDATE_VIEWPORT = 5;
private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6;
private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
+ private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
private final Context mContext;
private final DisplayManagerHandler mHandler;
@@ -237,6 +239,10 @@
private final CopyOnWriteArrayList<DisplayTransactionListener> mDisplayTransactionListeners =
new CopyOnWriteArrayList<DisplayTransactionListener>();
+ /** List of all display group listeners. */
+ private final CopyOnWriteArrayList<DisplayGroupListener> mDisplayGroupListeners =
+ new CopyOnWriteArrayList<>();
+
/** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */
private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
new SparseArray<>();
@@ -365,7 +371,7 @@
private final boolean mAllowNonNativeRefreshRateOverride;
- private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
+ private final BrightnessSynchronizer mBrightnessSynchronizer;
/**
* Applications use {@link android.view.Display#getRefreshRate} and
@@ -402,6 +408,7 @@
mLogicalDisplayMapper = new LogicalDisplayMapper(context, mDisplayDeviceRepo,
new LogicalDisplayListener());
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
+ mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources();
mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
@@ -536,6 +543,7 @@
mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
mSettingsObserver = new SettingsObserver();
+ mBrightnessSynchronizer.startSynchronizing();
}
@VisibleForTesting
@@ -1677,6 +1685,11 @@
mHandler.sendMessage(msg);
}
+ private void sendDisplayGroupEvent(int groupId, int event) {
+ Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_GROUP_EVENT, groupId, event);
+ mHandler.sendMessage(msg);
+ }
+
private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
@@ -1721,6 +1734,35 @@
mTempCallbacks.clear();
}
+ // Runs on Handler thread.
+ // Delivers display group event notifications to callbacks.
+ private void deliverDisplayGroupEvent(int groupId, int event) {
+ if (DEBUG) {
+ Slog.d(TAG, "Delivering display group event: groupId=" + groupId + ", event="
+ + event);
+ }
+
+ switch (event) {
+ case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED:
+ for (DisplayGroupListener listener : mDisplayGroupListeners) {
+ listener.onDisplayGroupAdded(groupId);
+ }
+ break;
+
+ case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED:
+ for (DisplayGroupListener listener : mDisplayGroupListeners) {
+ listener.onDisplayGroupChanged(groupId);
+ }
+ break;
+
+ case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED:
+ for (DisplayGroupListener listener : mDisplayGroupListeners) {
+ listener.onDisplayGroupRemoved(groupId);
+ }
+ break;
+ }
+ }
+
private IMediaProjectionManager getProjectionService() {
if (mProjectionService == null) {
IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
@@ -1943,6 +1985,11 @@
}
deliverDisplayEvent(msg.arg1, uids, msg.arg2);
break;
+
+ case MSG_DELIVER_DISPLAY_GROUP_EVENT:
+ deliverDisplayGroupEvent(msg.arg1, msg.arg2);
+ break;
+
}
}
}
@@ -1974,6 +2021,11 @@
}
@Override
+ public void onDisplayGroupEventLocked(int groupId, int event) {
+ sendDisplayGroupEvent(groupId, event);
+ }
+
+ @Override
public void onTraversalRequested() {
synchronized (mSyncRoot) {
scheduleTraversalLocked(false);
@@ -2708,7 +2760,7 @@
}
@Override
- public boolean requestPowerState(DisplayPowerRequest request,
+ public boolean requestPowerState(int groupId, DisplayPowerRequest request,
boolean waitForNegativeProximity) {
synchronized (mSyncRoot) {
return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
@@ -2732,6 +2784,16 @@
}
@Override
+ public void registerDisplayGroupListener(DisplayGroupListener listener) {
+ mDisplayGroupListeners.add(listener);
+ }
+
+ @Override
+ public void unregisterDisplayGroupListener(DisplayGroupListener listener) {
+ mDisplayGroupListeners.remove(listener);
+ }
+
+ @Override
public SurfaceControl.ScreenshotHardwareBuffer systemScreenshot(int displayId) {
return systemScreenshotInternal(displayId);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 13dc0b9..2df33652 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -47,6 +47,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.util.TimeUtils;
@@ -58,6 +59,8 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
+import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
@@ -152,6 +155,7 @@
private final DisplayPowerCallbacks mCallbacks;
// Battery stats.
+ @Nullable
private final IBatteryStats mBatteryStats;
// The sensor manager.
@@ -170,6 +174,7 @@
private final int mDisplayId;
// Tracker for brightness changes.
+ @Nullable
private final BrightnessTracker mBrightnessTracker;
// Tracker for brightness settings changes.
@@ -346,6 +351,9 @@
@Nullable
private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
+ private final ColorDisplayServiceInternal mCdsi;
+ private final float[] mNitsRange;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -401,30 +409,30 @@
private ObjectAnimator mColorFadeOffAnimator;
private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
- // The brightness synchronizer to allow changes in the int brightness value to be reflected in
- // the float brightness value and vice versa.
- @Nullable
- private final BrightnessSynchronizer mBrightnessSynchronizer;
-
/**
* Creates the display power controller.
*/
public DisplayPowerController(Context context,
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay) {
+ mLogicalDisplay = logicalDisplay;
+ mDisplayId = mLogicalDisplay.getDisplayIdLocked();
mHandler = new DisplayControllerHandler(handler.getLooper());
- mBrightnessTracker = new BrightnessTracker(context, null);
+
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mBrightnessTracker = new BrightnessTracker(context, null);
+ mBatteryStats = BatteryStatsService.getService();
+ } else {
+ mBrightnessTracker = null;
+ mBatteryStats = null;
+ }
+
mSettingsObserver = new SettingsObserver(mHandler);
mCallbacks = callbacks;
- mBatteryStats = BatteryStatsService.getService();
mSensorManager = sensorManager;
mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
mBlanker = blanker;
mContext = context;
- mBrightnessSynchronizer = new BrightnessSynchronizer(context);
- mBrightnessSynchronizer.startSynchronizing();
- mLogicalDisplay = logicalDisplay;
- mDisplayId = mLogicalDisplay.getDisplayIdLocked();
PowerManager pm = context.getSystemService(PowerManager.class);
@@ -455,8 +463,12 @@
mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
+ // Check the setting, but also verify that it is the default display. Only the default
+ // display has an automatic brightness controller running.
+ // TODO: b/179021925 - Fix to work with multiple displays
mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
- com.android.internal.R.bool.config_automatic_brightness_available);
+ com.android.internal.R.bool.config_automatic_brightness_available)
+ && mDisplayId == Display.DEFAULT_DISPLAY;
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
@@ -552,7 +564,9 @@
mBrightnessBucketsInDozeConfig = resources.getBoolean(
com.android.internal.R.bool.config_displayBrightnessBucketsInDoze);
- if (!DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ if (mDisplayId == Display.DEFAULT_DISPLAY && !DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ // TODO: b/178385123 Once there are sensor associations, we can enable proximity for
+ // non-default displays.
mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (mProximitySensor != null) {
mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
@@ -580,6 +594,42 @@
}
mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
mDisplayWhiteBalanceController = displayWhiteBalanceController;
+
+ if (displayDeviceConfig != null && displayDeviceConfig.getNits() != null) {
+ mNitsRange = displayDeviceConfig.getNits();
+ } else {
+ Slog.w(TAG, "Screen brightness nits configuration is unavailable; falling back");
+ mNitsRange = BrightnessMappingStrategy.getFloatArray(context.getResources()
+ .obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits));
+ }
+ mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
+ boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
+ @Override
+ public void onReduceBrightColorsActivationChanged(boolean activated) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+
+ @Override
+ public void onReduceBrightColorsStrengthChanged(int strength) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+ });
+ if (active) {
+ applyReduceBrightColorsSplineAdjustment();
+ }
+ }
+
+ private void applyReduceBrightColorsSplineAdjustment() {
+ if (mBrightnessMapper == null) {
+ Log.w(TAG, "No brightness mapping available to recalculate splines");
+ return;
+ }
+
+ float[] adjustedNits = new float[mNitsRange.length];
+ for (int i = 0; i < mNitsRange.length; i++) {
+ adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
+ }
+ mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits);
}
private Sensor findDisplayLightSensor(String sensorType) {
@@ -607,18 +657,28 @@
* @param userId userId to fetch data for
* @param includePackage if false will null out the package name in events
*/
+ @Nullable
public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
@UserIdInt int userId, boolean includePackage) {
+ if (mBrightnessTracker == null) {
+ return null;
+ }
return mBrightnessTracker.getEvents(userId, includePackage);
}
public void onSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
- mBrightnessTracker.onSwitchUser(newUserId);
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker.onSwitchUser(newUserId);
+ }
}
+ @Nullable
public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
@UserIdInt int userId) {
+ if (mBrightnessTracker == null) {
+ return null;
+ }
return mBrightnessTracker.getAmbientBrightnessStats(userId);
}
@@ -626,7 +686,9 @@
* Persist the brightness slider events and ambient brightness stats to disk.
*/
public void persistBrightnessTrackerState() {
- mBrightnessTracker.persistBrightnessTrackerState();
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker.persistBrightnessTrackerState();
+ }
}
/**
@@ -730,20 +792,16 @@
mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT);
mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
- // Initialize screen state for battery stats.
- try {
- mBatteryStats.noteScreenState(mPowerState.getScreenState());
- mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
- mPowerState.getScreenBrightness()));
- } catch (RemoteException ex) {
- // same process
- }
+ noteScreenState(mPowerState.getScreenState());
+ noteScreenBrightness(mPowerState.getScreenBrightness());
+
// Initialize all of the brightness tracking state
final float brightness = convertToNits(BrightnessSynchronizer.brightnessFloatToInt(
mPowerState.getScreenBrightness()));
if (brightness >= 0.0f) {
mBrightnessTracker.start(brightness);
}
+
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
@@ -1330,11 +1388,7 @@
SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
mPowerState.setScreenState(state);
// Tell battery stats about the transition.
- try {
- mBatteryStats.noteScreenState(state);
- } catch (RemoteException ex) {
- // same process
- }
+ noteScreenState(state);
}
}
@@ -1406,13 +1460,7 @@
Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
// TODO(b/153319140) remove when we can get this from the above trace invocation
SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
- try {
- // TODO(brightnessfloat): change BatteryStats to use float
- mBatteryStats.noteScreenBrightness(
- BrightnessSynchronizer.brightnessFloatToInt(target));
- } catch (RemoteException ex) {
- // same process
- }
+ noteScreenBrightness(target);
}
}
@@ -1731,15 +1779,21 @@
}
private void putScreenBrightnessSetting(float brightnessValue) {
- mCurrentScreenBrightnessSetting = brightnessValue;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessValue, UserHandle.USER_CURRENT);
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mCurrentScreenBrightnessSetting = brightnessValue;
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessValue,
+ UserHandle.USER_CURRENT);
+ }
}
private void putAutoBrightnessAdjustmentSetting(float adjustment) {
- mAutoBrightnessAdjustment = adjustment;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment, UserHandle.USER_CURRENT);
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mAutoBrightnessAdjustment = adjustment;
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+ UserHandle.USER_CURRENT);
+ }
}
private boolean updateAutoBrightnessAdjustment() {
@@ -2020,6 +2074,29 @@
return MathUtils.constrain(value, -1.0f, 1.0f);
}
+ private void noteScreenState(int screenState) {
+ if (mBatteryStats != null) {
+ try {
+ // TODO(multi-display): make this multi-display
+ mBatteryStats.noteScreenState(screenState);
+ } catch (RemoteException e) {
+ // same process
+ }
+ }
+ }
+
+ private void noteScreenBrightness(float brightness) {
+ if (mBatteryStats != null) {
+ try {
+ // TODO(brightnessfloat): change BatteryStats to use float
+ mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
+ brightness));
+ } catch (RemoteException e) {
+ // same process
+ }
+ }
+ }
+
private final class DisplayControllerHandler extends Handler {
public DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8198e51..7e6a137 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -35,6 +35,7 @@
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
+import android.view.RoundedCorners;
import android.view.SurfaceControl;
import com.android.internal.BrightnessSynchronizer;
@@ -604,6 +605,8 @@
}
mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
mInfo.width, mInfo.height);
+ mInfo.roundedCorners = RoundedCorners.fromResources(
+ res, mInfo.width, mInfo.height);
} else {
if (!res.getBoolean(
com.android.internal.R.bool.config_localDisplaysMirrorContent)) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 86de159..80781d2 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -195,6 +195,7 @@
info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
info.physicalXDpi = mOverrideDisplayInfo.physicalXDpi;
info.physicalYDpi = mOverrideDisplayInfo.physicalYDpi;
+ info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
}
mInfo.set(info);
}
@@ -386,6 +387,7 @@
mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum;
mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
+ mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo.set(null);
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index e738878..16c4b26 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -55,6 +55,10 @@
public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
+ public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
+ public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
+ public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
+
/**
* Temporary display info, used for comparing display configurations.
*/
@@ -99,11 +103,11 @@
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
/** A mapping from logical display id to display group. */
- private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
+ private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
private final DisplayDeviceRepository mDisplayDeviceRepo;
private final Listener mListener;
- private final int mFoldedDeviceState;
+ private final int[] mFoldedDeviceStates;
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
mDisplayDeviceRepo = repo;
@@ -111,8 +115,8 @@
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mDisplayDeviceRepo.addListener(this);
- mFoldedDeviceState = context.getResources().getInteger(
- com.android.internal.R.integer.config_foldedDeviceState);
+ mFoldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
loadFoldedDisplayConfig(context);
}
@@ -183,7 +187,7 @@
}
public int getDisplayGroupIdLocked(int displayId) {
- final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+ final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
if (displayGroup != null) {
return displayGroup.getGroupId();
}
@@ -191,6 +195,18 @@
return -1;
}
+ public DisplayGroup getDisplayGroupLocked(int groupId) {
+ final int size = mDisplayIdToGroupMap.size();
+ for (int i = 0; i < size; i++) {
+ final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i);
+ if (displayGroup.getGroupId() == groupId) {
+ return displayGroup;
+ }
+ }
+
+ return null;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("LogicalDisplayMapper:");
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
@@ -216,7 +232,14 @@
}
void setDeviceStateLocked(int state) {
- setDeviceFoldedLocked(state == mFoldedDeviceState);
+ boolean folded = false;
+ for (int i = 0; i < mFoldedDeviceStates.length; i++) {
+ if (state == mFoldedDeviceStates[i]) {
+ folded = true;
+ break;
+ }
+ }
+ setDeviceFoldedLocked(folded);
}
void setDeviceFoldedLocked(boolean isFolded) {
@@ -320,7 +343,7 @@
final int groupId = assignDisplayGroupIdLocked(isDefault);
displayGroup = new DisplayGroup(groupId);
} else {
- displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY);
+ displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY);
}
LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
@@ -335,11 +358,23 @@
mLogicalDisplays.put(displayId, display);
- displayGroup.addDisplay(display);
- mDisplayGroups.append(displayId, displayGroup);
+ displayGroup.addDisplayLocked(display);
+ mDisplayIdToGroupMap.append(displayId, displayGroup);
+
+ if (addNewDisplayGroup) {
+ // Group added events happen before Logical Display added events.
+ mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+ LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
+ }
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
+
+ if (!addNewDisplayGroup) {
+ // Group changed events happen after Logical Display added events.
+ mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+ LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+ }
}
/**
@@ -356,33 +391,47 @@
DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
display.getFrameRateOverrides();
display.updateLocked(mDisplayDeviceRepo);
+ final DisplayGroup changedDisplayGroup;
if (!display.isValidLocked()) {
mLogicalDisplays.removeAt(i);
- mDisplayGroups.removeReturnOld(displayId).removeDisplay(display);
+ final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId);
+ displayGroup.removeDisplayLocked(display);
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
+
+ changedDisplayGroup = displayGroup;
} else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
final int flags = display.getDisplayInfoLocked().flags;
- final DisplayGroup defaultDisplayGroup = mDisplayGroups.get(
+ final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get(
Display.DEFAULT_DISPLAY);
if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
// The display should have its own DisplayGroup.
- if (defaultDisplayGroup.removeDisplay(display)) {
+ if (defaultDisplayGroup.removeDisplayLocked(display)) {
final int groupId = assignDisplayGroupIdLocked(false);
final DisplayGroup displayGroup = new DisplayGroup(groupId);
- displayGroup.addDisplay(display);
- mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup);
+ displayGroup.addDisplayLocked(display);
display.updateDisplayGroupIdLocked(groupId);
+ mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup);
+ mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+ LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
+ changedDisplayGroup = defaultDisplayGroup;
+ } else {
+ changedDisplayGroup = null;
}
} else {
// The display should be a part of the default DisplayGroup.
- final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+ final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
if (displayGroup != defaultDisplayGroup) {
- displayGroup.removeDisplay(display);
- defaultDisplayGroup.addDisplay(display);
- mDisplayGroups.put(displayId, defaultDisplayGroup);
+ displayGroup.removeDisplayLocked(display);
+ defaultDisplayGroup.addDisplayLocked(display);
display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId());
+ mListener.onDisplayGroupEventLocked(defaultDisplayGroup.getGroupId(),
+ LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+ mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup);
+ changedDisplayGroup = displayGroup;
+ } else {
+ changedDisplayGroup = null;
}
}
@@ -394,6 +443,7 @@
} else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+ changedDisplayGroup = null;
} else {
// While applications shouldn't know nor care about the non-overridden info, we
// still need to let WindowManager know so it can update its own internal state for
@@ -403,6 +453,15 @@
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
}
+ changedDisplayGroup = null;
+ }
+
+ // CHANGED and REMOVED DisplayGroup events should always fire after Display events.
+ if (changedDisplayGroup != null) {
+ final int event = changedDisplayGroup.isEmptyLocked()
+ ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED
+ : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED;
+ mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event);
}
}
}
@@ -438,6 +497,7 @@
public interface Listener {
void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
+ void onDisplayGroupEventLocked(int groupId, int event);
void onTraversalRequested();
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 4706edc..88b2668 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -173,6 +173,7 @@
private ContentObserver mContentObserver;
private DisplayWhiteBalanceListener mDisplayWhiteBalanceListener;
+ private ReduceBrightColorsListener mReduceBrightColorsListener;
private NightDisplayAutoMode mNightDisplayAutoMode;
@@ -617,18 +618,24 @@
if (mCurrentUser == UserHandle.USER_NULL) {
return;
}
- mReduceBrightColorsTintController.setActivated(
- Secure.getIntForUser(getContext().getContentResolver(),
- Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0, mCurrentUser) == 1);
+ final boolean activated = Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0, mCurrentUser) == 1;
+ mReduceBrightColorsTintController.setActivated(activated);
+ if (mReduceBrightColorsListener != null) {
+ mReduceBrightColorsListener.onReduceBrightColorsActivationChanged(activated);
+ }
}
private void onReduceBrightColorsStrengthLevelChanged() {
if (mCurrentUser == UserHandle.USER_NULL) {
return;
}
- mReduceBrightColorsTintController.setMatrix(
- Secure.getIntForUser(getContext().getContentResolver(),
- Secure.REDUCE_BRIGHT_COLORS_LEVEL, 0, mCurrentUser));
+ final int strength = Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.REDUCE_BRIGHT_COLORS_LEVEL, 0, mCurrentUser);
+ mReduceBrightColorsTintController.setMatrix(strength);
+ if (mReduceBrightColorsListener != null) {
+ mReduceBrightColorsListener.onReduceBrightColorsStrengthChanged(strength);
+ }
}
/**
@@ -762,6 +769,22 @@
mCurrentUser) == 1;
}
+ private boolean setReduceBrightColorsActivatedInternal(boolean activated) {
+ if (mCurrentUser == UserHandle.USER_NULL) {
+ return false;
+ }
+ return Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, activated ? 1 : 0, mCurrentUser);
+ }
+
+ private boolean setReduceBrightColorsStrengthInternal(int strength) {
+ if (mCurrentUser == UserHandle.USER_NULL) {
+ return false;
+ }
+ return Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.REDUCE_BRIGHT_COLORS_LEVEL, strength, mCurrentUser);
+ }
+
private boolean isDeviceColorManagedInternal() {
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
return dtm.isDeviceColorManaged();
@@ -1469,6 +1492,31 @@
}
/**
+ * Sets the listener and returns whether reduce bright colors is currently enabled.
+ */
+ public boolean setReduceBrightColorsListener(ReduceBrightColorsListener listener) {
+ mReduceBrightColorsListener = listener;
+ return mReduceBrightColorsTintController.isActivated();
+ }
+
+ /**
+ * Returns whether reduce bright colors is currently active.
+ */
+ public boolean isReduceBrightColorsActivated() {
+ return mReduceBrightColorsTintController.isActivated();
+ }
+
+ /**
+ * Gets the computed brightness, in nits, when the reduce bright colors feature is applied
+ * at the current strength.
+ *
+ * @hide
+ */
+ public float getReduceBrightColorsAdjustedBrightnessNits(float nits) {
+ return mReduceBrightColorsTintController.getAdjustedBrightness(nits);
+ }
+
+ /**
* Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
* invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
*/
@@ -1491,6 +1539,22 @@
void onDisplayWhiteBalanceStatusChanged(boolean activated);
}
+ /**
+ * Listener for changes in reduce bright colors status.
+ */
+ public interface ReduceBrightColorsListener {
+
+ /**
+ * Notify that the reduce bright colors activation status has changed.
+ */
+ void onReduceBrightColorsActivationChanged(boolean activated);
+
+ /**
+ * Notify that the reduce bright colors strength has changed.
+ */
+ void onReduceBrightColorsStrengthChanged(int strength);
+ }
+
private final class TintHandler extends Handler {
private TintHandler(Looper looper) {
@@ -1787,6 +1851,62 @@
}
@Override
+ public boolean isReduceBrightColorsActivated() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mReduceBrightColorsTintController.isActivated();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setReduceBrightColorsActivated(boolean activated) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set reduce bright colors activation state");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return setReduceBrightColorsActivatedInternal(activated);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public int getReduceBrightColorsStrength() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mReduceBrightColorsTintController.getStrength();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public float getReduceBrightColorsOffsetFactor() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mReduceBrightColorsTintController.getOffsetFactor();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setReduceBrightColorsStrength(int strength) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set reduce bright colors strength");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return setReduceBrightColorsStrengthInternal(strength);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
diff --git a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
index 7e120c9..cb93cc8 100644
--- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
+++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
@@ -34,7 +34,7 @@
public class ReduceBrightColorsTintController extends TintController {
private final float[] mMatrix = new float[16];
- private final float[] mCoefficients = new float[9];
+ private final float[] mCoefficients = new float[3];
private int mStrength;
@@ -42,8 +42,8 @@
public void setUp(Context context, boolean needsLinear) {
final String[] coefficients = context.getResources().getStringArray(
needsLinear ? R.array.config_reduceBrightColorsCoefficients
- : R.array.config_reduceBrightColorsCoefficientsNative);
- for (int i = 0; i < 9 && i < coefficients.length; i++) {
+ : R.array.config_reduceBrightColorsCoefficientsNonlinear);
+ for (int i = 0; i < 3 && i < coefficients.length; i++) {
mCoefficients[i] = Float.parseFloat(coefficients[i]);
}
}
@@ -67,20 +67,11 @@
Matrix.setIdentityM(mMatrix, 0);
- final float percentageStrength = strengthLevel / 100f;
- final float squaredPercentageStrength = percentageStrength * percentageStrength;
- final float red =
- squaredPercentageStrength * mCoefficients[0] + percentageStrength * mCoefficients[1]
- + mCoefficients[2];
- final float green =
- squaredPercentageStrength * mCoefficients[3] + percentageStrength * mCoefficients[4]
- + mCoefficients[5];
- final float blue =
- squaredPercentageStrength * mCoefficients[6] + percentageStrength * mCoefficients[7]
- + mCoefficients[8];
- mMatrix[0] = clamp(red);
- mMatrix[5] = clamp(green);
- mMatrix[10] = clamp(blue);
+ // All three (r,g,b) components are equal and calculated with the same formula.
+ final float componentValue = computeComponentValue(strengthLevel);
+ mMatrix[0] = componentValue;
+ mMatrix[5] = componentValue;
+ mMatrix[10] = componentValue;
}
private float clamp(float value) {
@@ -110,4 +101,26 @@
public int getStrength() {
return mStrength;
}
+
+ /** Returns the offset factor at Ymax. */
+ public float getOffsetFactor() {
+ // Strength terms drop out as strength --> 1, leaving the coefficients.
+ return mCoefficients[0] + mCoefficients[1] + mCoefficients[2];
+ }
+
+ /**
+ * Returns the effective brightness (in nits), which has been adjusted to account for the effect
+ * of the bright color reduction.
+ */
+ public float getAdjustedBrightness(float nits) {
+ return computeComponentValue(mStrength) * nits;
+ }
+
+ private float computeComponentValue(int strengthLevel) {
+ final float percentageStrength = strengthLevel / 100f;
+ final float squaredPercentageStrength = percentageStrength * percentageStrength;
+ return clamp(
+ squaredPercentageStrength * mCoefficients[0] + percentageStrength * mCoefficients[1]
+ + mCoefficients[2]);
+ }
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
new file mode 100644
index 0000000..b082b25
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A class to detect font-related native crash.
+ *
+ * <p>If a fs-verity protected file is accessed through mmap and corrupted file block is detected,
+ * SIGBUG signal is generated and the process will crash. To find corrupted files and remove them,
+ * we use a marker file to detect crash.
+ * <ol>
+ * <li>Create a marker file before reading fs-verity protected font files.
+ * <li>Delete the marker file after reading font files successfully.
+ * <li>If the marker file is found in the next process startup, it means that the process
+ * crashed before. We will delete font files to prevent crash loop.
+ * </ol>
+ *
+ * <p>Example usage:
+ * <pre>
+ * FontCrashDetector detector = new FontCrashDetector(new File("/path/to/marker_file"));
+ * if (detector.hasCrashed()) {
+ * // Do cleanup
+ * }
+ * try (FontCrashDetector.MonitoredBlock b = detector.start()) {
+ * // Read files
+ * }
+ * </pre>
+ *
+ * <p>This class DOES NOT detect Java exceptions. If a Java exception is thrown while monitoring
+ * crash, the marker file will be deleted. Creating and deleting marker files are not lightweight.
+ * Please use this class sparingly with caution.
+ */
+/* package */ final class FontCrashDetector {
+
+ private static final String TAG = "FontCrashDetector";
+
+ @NonNull
+ private final File mMarkerFile;
+
+ /* package */ FontCrashDetector(@NonNull File markerFile) {
+ mMarkerFile = markerFile;
+ }
+
+ /* package */ boolean hasCrashed() {
+ return mMarkerFile.exists();
+ }
+
+ /* package */ void clear() {
+ if (!mMarkerFile.delete()) {
+ Slog.e(TAG, "Could not delete marker file: " + mMarkerFile);
+ }
+ }
+
+ /** Starts crash monitoring. */
+ /* package */ MonitoredBlock start() {
+ try {
+ mMarkerFile.createNewFile();
+ } catch (IOException e) {
+ Slog.e(TAG, "Could not create marker file: " + mMarkerFile, e);
+ }
+ return new MonitoredBlock();
+ }
+
+ /** A helper class to monitor crash with try-with-resources syntax. */
+ /* package */ class MonitoredBlock implements AutoCloseable {
+ /** Ends crash monitoring. */
+ @Override
+ public void close() {
+ clear();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 7461405..3e2b5ab 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -24,9 +24,8 @@
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontFileUtil;
import android.graphics.fonts.FontManager;
+import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.SystemFonts;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SharedMemory;
import android.os.ShellCallback;
@@ -55,6 +54,7 @@
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -64,22 +64,23 @@
private static final String TAG = "FontManagerService";
private static final String FONT_FILES_DIR = "/data/fonts/files";
+ private static final String CRASH_MARKER_FILE = "/data/fonts/config/crash.txt";
@Override
- public FontConfig getFontConfig() throws RemoteException {
+ public FontConfig getFontConfig() {
return getSystemFontConfig();
}
@Override
- public int updateFont(ParcelFileDescriptor fd, byte[] signature, int baseVersion)
- throws RemoteException {
- Objects.requireNonNull(fd);
- Objects.requireNonNull(signature);
+ public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(request.getFd());
+ Objects.requireNonNull(request.getSignature());
Preconditions.checkArgumentNonnegative(baseVersion);
getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
"UPDATE_FONTS permission required.");
try {
- installFontFile(fd.getFileDescriptor(), signature, baseVersion);
+ update(baseVersion, Collections.singletonList(request));
return FontManager.RESULT_SUCCESS;
} catch (SystemFontException e) {
Slog.e(TAG, "Failed to update font file", e);
@@ -179,16 +180,32 @@
}
}
+ @NonNull
+ private final Context mContext;
+
+ private final Object mUpdatableFontDirLock = new Object();
+
+ @GuardedBy("mUpdatableFontDirLock")
+ @NonNull
+ private final FontCrashDetector mFontCrashDetector;
+
+ @GuardedBy("mUpdatableFontDirLock")
@Nullable
private final UpdatableFontDir mUpdatableFontDir;
- @GuardedBy("FontManagerService.this")
+ // mSerializedFontMapLock can be acquired while holding mUpdatableFontDirLock.
+ // mUpdatableFontDirLock should not be newly acquired while holding mSerializedFontMapLock.
+ private final Object mSerializedFontMapLock = new Object();
+
+ @GuardedBy("mSerializedFontMapLock")
@Nullable
private SharedMemory mSerializedFontMap = null;
private FontManagerService(Context context) {
mContext = context;
+ mFontCrashDetector = new FontCrashDetector(new File(CRASH_MARKER_FILE));
mUpdatableFontDir = createUpdatableFontDir();
+ initialize();
}
@Nullable
@@ -201,32 +218,53 @@
new OtfFontFileParser(), new FsverityUtilImpl());
}
-
- @NonNull
- private final Context mContext;
+ private void initialize() {
+ synchronized (mUpdatableFontDirLock) {
+ if (mUpdatableFontDir == null) {
+ synchronized (mSerializedFontMapLock) {
+ try {
+ mSerializedFontMap = Typeface.serializeFontMap(Typeface.getSystemFontMap());
+ } catch (IOException | ErrnoException e) {
+ mSerializedFontMap = null;
+ }
+ }
+ return;
+ }
+ if (mFontCrashDetector.hasCrashed()) {
+ Slog.i(TAG, "Crash detected. Clearing font updates.");
+ try {
+ mUpdatableFontDir.clearUpdates();
+ } catch (SystemFontException e) {
+ Slog.e(TAG, "Failed to clear updates.", e);
+ }
+ mFontCrashDetector.clear();
+ }
+ try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+ mUpdatableFontDir.loadFontFileMap();
+ updateSerializedFontMap();
+ }
+ }
+ }
@NonNull
public Context getContext() {
return mContext;
}
- @NonNull /* package */ SharedMemory getCurrentFontMap() {
- synchronized (FontManagerService.this) {
- if (mSerializedFontMap == null) {
- mSerializedFontMap = buildNewSerializedFontMap();
- }
+ @Nullable /* package */ SharedMemory getCurrentFontMap() {
+ synchronized (mSerializedFontMapLock) {
return mSerializedFontMap;
}
}
- /* package */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature, int baseVersion)
+ /* package */ void update(int baseVersion, List<FontUpdateRequest> requests)
throws SystemFontException {
if (mUpdatableFontDir == null) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
"The font updater is disabled.");
}
- synchronized (FontManagerService.this) {
+ synchronized (mUpdatableFontDirLock) {
// baseVersion == -1 only happens from shell command. This is filtered and treated as
// error from SystemApi call.
if (baseVersion != -1 && mUpdatableFontDir.getConfigVersion() != baseVersion) {
@@ -234,9 +272,10 @@
FontManager.RESULT_ERROR_VERSION_MISMATCH,
"The base config version is older than current.");
}
- mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
- // Create updated font map in the next getSerializedSystemFontMap() call.
- mSerializedFontMap = null;
+ try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+ mUpdatableFontDir.update(requests);
+ updateSerializedFontMap();
+ }
}
}
@@ -246,13 +285,19 @@
FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
"The font updater is disabled.");
}
- mUpdatableFontDir.clearUpdates();
+ synchronized (mUpdatableFontDirLock) {
+ try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+ mUpdatableFontDir.clearUpdates();
+ updateSerializedFontMap();
+ }
+ }
}
/* package */ Map<String, File> getFontFileMap() {
if (mUpdatableFontDir == null) {
return Collections.emptyMap();
- } else {
+ }
+ synchronized (mUpdatableFontDirLock) {
return mUpdatableFontDir.getFontFileMap();
}
}
@@ -270,7 +315,7 @@
@Nullable FileDescriptor err,
@NonNull String[] args,
@Nullable ShellCallback callback,
- @NonNull ResultReceiver result) throws RemoteException {
+ @NonNull ResultReceiver result) {
new FontManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
}
@@ -278,24 +323,28 @@
* Returns an active system font configuration.
*/
public @NonNull FontConfig getSystemFontConfig() {
- if (mUpdatableFontDir != null) {
- return mUpdatableFontDir.getSystemFontConfig();
- } else {
+ if (mUpdatableFontDir == null) {
return SystemFonts.getSystemPreinstalledFontConfig();
}
+ synchronized (mUpdatableFontDirLock) {
+ return mUpdatableFontDir.getSystemFontConfig();
+ }
}
/**
- * Make new serialized font map data.
+ * Makes new serialized font map data and updates mSerializedFontMap.
*/
- public @Nullable SharedMemory buildNewSerializedFontMap() {
+ public void updateSerializedFontMap() {
try {
final FontConfig fontConfig = getSystemFontConfig();
final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig);
final Map<String, Typeface> typefaceMap =
SystemFonts.buildSystemTypefaces(fontConfig, fallback);
- return Typeface.serializeFontMap(typefaceMap);
+ SharedMemory serializeFontMap = Typeface.serializeFontMap(typefaceMap);
+ synchronized (mSerializedFontMapLock) {
+ mSerializedFontMap = serializeFontMap;
+ }
} catch (IOException | ErrnoException e) {
Slog.w(TAG, "Failed to serialize updatable font map. "
+ "Retrying with system image fonts.", e);
@@ -307,11 +356,13 @@
final Map<String, Typeface> typefaceMap =
SystemFonts.buildSystemTypefaces(fontConfig, fallback);
- return Typeface.serializeFontMap(typefaceMap);
+ SharedMemory serializeFontMap = Typeface.serializeFontMap(typefaceMap);
+ synchronized (mSerializedFontMapLock) {
+ mSerializedFontMap = serializeFontMap;
+ }
} catch (IOException | ErrnoException e) {
Slog.e(TAG, "Failed to serialize SystemServer system font map", e);
}
- return null;
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index d2111e7..cf9a79f 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -24,6 +24,7 @@
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontManager;
+import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.fonts.SystemFonts;
import android.os.Binder;
@@ -44,6 +45,7 @@
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -169,8 +171,9 @@
sb.append(c++);
sb.append("]: lang=\"");
sb.append(family.getLocaleList().toLanguageTags());
+ sb.append("\"");
if (family.getVariant() != FontConfig.FontFamily.VARIANT_DEFAULT) {
- sb.append("\", variant=");
+ sb.append(", variant=");
switch (family.getVariant()) {
case FontConfig.FontFamily.VARIANT_COMPACT:
sb.append("Compact");
@@ -314,6 +317,7 @@
"Signature file argument is required.");
}
+ // TODO: close fontFd and sigFd.
ParcelFileDescriptor fontFd = shell.openFileForSystem(fontPath, "r");
if (fontFd == null) {
throw new SystemFontException(
@@ -329,29 +333,24 @@
}
try (FileInputStream sigFis = new FileInputStream(sigFd.getFileDescriptor())) {
- try (FileInputStream fontFis = new FileInputStream(fontFd.getFileDescriptor())) {
- int len = sigFis.available();
- if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_SIGNATURE_TOO_LARGE,
- "Signature file is too large");
- }
- byte[] signature = new byte[len];
- if (sigFis.read(signature, 0, len) != len) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
- "Invalid read length");
- }
- mService.installFontFile(fontFis.getFD(), signature, -1);
- } catch (IOException e) {
+ int len = sigFis.available();
+ if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_SIGNATURE_TOO_LARGE,
+ "Signature file is too large");
+ }
+ byte[] signature = new byte[len];
+ if (sigFis.read(signature, 0, len) != len) {
throw new SystemFontException(
FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
- "Failed to read signature file.", e);
+ "Invalid read length");
}
+ mService.update(
+ -1, Collections.singletonList(new FontUpdateRequest(fontFd, signature)));
} catch (IOException e) {
throw new SystemFontException(
- FontManager.RESULT_ERROR_INVALID_FONT_FILE,
- "Failed to read font files.", e);
+ FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
+ "Failed to read signature file.", e);
}
shell.getOutPrintWriter().println("Success"); // TODO: Output more details.
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 f0d14ba..017f11c 100644
--- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -29,24 +30,19 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Set;
/* package */ class PersistentSystemFontConfig {
private static final String TAG = "PersistentSystemFontConfig";
private static final String TAG_ROOT = "fontConfig";
private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
- private static final String TAG_VALUE = "value";
+ private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir";
+ private static final String ATTR_VALUE = "value";
/* package */ static class Config {
public long lastModifiedDate;
-
- public void reset() {
- lastModifiedDate = 0;
- }
-
- public void copyTo(@NonNull Config out) {
- out.lastModifiedDate = lastModifiedDate;
- }
+ public final Set<String> updatedFontDirs = new ArraySet<>();
}
/**
@@ -54,7 +50,6 @@
*/
public static void loadFromXml(@NonNull InputStream is, @NonNull Config out)
throws XmlPullParserException, IOException {
- out.reset();
TypedXmlPullParser parser = Xml.resolvePullParser(is);
int type;
@@ -72,7 +67,10 @@
} else if (depth == 2) {
switch (tag) {
case TAG_LAST_MODIFIED_DATE:
- out.lastModifiedDate = parseLongAttribute(parser, TAG_VALUE, 0);
+ out.lastModifiedDate = parseLongAttribute(parser, ATTR_VALUE, 0);
+ break;
+ case TAG_UPDATED_FONT_DIR:
+ out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE));
break;
default:
Slog.w(TAG, "Skipping unknown tag: " + tag);
@@ -92,8 +90,13 @@
out.startTag(null, TAG_ROOT);
out.startTag(null, TAG_LAST_MODIFIED_DATE);
- out.attribute(null, TAG_VALUE, Long.toString(config.lastModifiedDate));
+ out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedDate));
out.endTag(null, TAG_LAST_MODIFIED_DATE);
+ for (String dir : config.updatedFontDirs) {
+ out.startTag(null, TAG_UPDATED_FONT_DIR);
+ out.attribute(null, ATTR_VALUE, dir);
+ out.endTag(null, TAG_UPDATED_FONT_DIR);
+ }
out.endTag(null, TAG_ROOT);
out.endDocument();
@@ -111,4 +114,9 @@
}
}
+ @NonNull
+ private static String getAttribute(TypedXmlPullParser parser, String attr) {
+ final String value = parser.getAttributeValue(null /* namespace */, attr);
+ return value == null ? "" : value;
+ }
}
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 b0bc65b..afc97c7 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.fonts.FontManager;
+import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.SystemFonts;
import android.os.FileUtils;
import android.system.ErrnoException;
@@ -29,8 +30,6 @@
import android.util.Base64;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
-
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
@@ -44,6 +43,11 @@
import java.util.List;
import java.util.Map;
+/**
+ * Manages set of updatable font files.
+ *
+ * <p>This class is not thread safe.
+ */
final class UpdatableFontDir {
private static final String TAG = "UpdatableFontDir";
@@ -69,15 +73,6 @@
boolean rename(File src, File dest);
}
- /** Interface to mock persistent configuration */
- interface PersistentConfig {
- void loadFromXml(PersistentSystemFontConfig.Config out)
- throws XmlPullParserException, IOException;
- void writeToXml(PersistentSystemFontConfig.Config config)
- throws IOException;
- boolean rename(File src, File dest);
- }
-
/** Data class to hold font file path and revision. */
private static final class FontFileInfo {
private final File mFile;
@@ -113,11 +108,7 @@
private final File mConfigFile;
private final File mTmpConfigFile;
- @GuardedBy("UpdatableFontDir.this")
- private final PersistentSystemFontConfig.Config mConfig =
- new PersistentSystemFontConfig.Config();
-
- @GuardedBy("UpdatableFontDir.this")
+ private long mLastModifiedDate;
private int mConfigVersion = 1;
/**
@@ -125,7 +116,6 @@
* FontFileInfo}. All files in this map are validated, and have higher revision numbers than
* corresponding font files in {@link #mPreinstalledFontDirs}.
*/
- @GuardedBy("UpdatableFontDir.this")
private final Map<String, FontFileInfo> mFontFileInfoMap = new HashMap<>();
UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
@@ -142,61 +132,110 @@
mFsverityUtil = fsverityUtil;
mConfigFile = configFile;
mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
- loadFontFileMap();
}
- private void loadFontFileMap() {
- // TODO: SIGBUS crash protection
- synchronized (UpdatableFontDir.this) {
- boolean success = false;
-
+ /* package */ void loadFontFileMap() {
+ mFontFileInfoMap.clear();
+ mLastModifiedDate = 0;
+ boolean success = false;
+ try {
+ PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
try (FileInputStream fis = new FileInputStream(mConfigFile)) {
- PersistentSystemFontConfig.loadFromXml(fis, mConfig);
+ PersistentSystemFontConfig.loadFromXml(fis, config);
} catch (IOException | XmlPullParserException e) {
- mConfig.reset();
+ Slog.e(TAG, "Failed to load config xml file", e);
+ return;
}
+ mLastModifiedDate = config.lastModifiedDate;
- mFontFileInfoMap.clear();
- try {
- File[] dirs = mFilesDir.listFiles();
- if (dirs == null) return;
- for (File dir : dirs) {
- if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) return;
- File[] files = dir.listFiles();
- if (files == null || files.length != 1) return;
- FontFileInfo fontFileInfo = validateFontFile(files[0]);
- addFileToMapIfNewerLocked(fontFileInfo, true /* deleteOldFile */);
+ File[] dirs = mFilesDir.listFiles();
+ if (dirs == null) return;
+ for (File dir : dirs) {
+ if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) {
+ Slog.e(TAG, "Unexpected dir found: " + dir);
+ return;
}
- success = true;
- } catch (Throwable t) {
- // If something happened during loading system fonts, clear all contents in finally
- // block. Here, just dumping errors.
- Slog.e(TAG, "Failed to load font mappings.", t);
- } finally {
- // Delete all files just in case if we find a problematic file.
- if (!success) {
- mFontFileInfoMap.clear();
- FileUtils.deleteContents(mFilesDir);
+ if (!config.updatedFontDirs.contains(dir.getName())) {
+ Slog.i(TAG, "Deleting obsolete dir: " + dir);
+ FileUtils.deleteContentsAndDir(dir);
+ continue;
}
+ File[] files = dir.listFiles();
+ if (files == null || files.length != 1) {
+ Slog.e(TAG, "Unexpected files in dir: " + dir);
+ return;
+ }
+ FontFileInfo fontFileInfo = validateFontFile(files[0]);
+ addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */);
+ }
+ success = true;
+ } catch (Throwable t) {
+ // If something happened during loading system fonts, clear all contents in finally
+ // block. Here, just dumping errors.
+ Slog.e(TAG, "Failed to load font mappings.", t);
+ } finally {
+ // Delete all files just in case if we find a problematic file.
+ if (!success) {
+ mFontFileInfoMap.clear();
+ mLastModifiedDate = 0;
+ FileUtils.deleteContents(mFilesDir);
}
}
}
/* package */ void clearUpdates() throws SystemFontException {
- synchronized (UpdatableFontDir.this) {
- mFontFileInfoMap.clear();
- FileUtils.deleteContents(mFilesDir);
+ mFontFileInfoMap.clear();
+ FileUtils.deleteContents(mFilesDir);
- mConfig.reset();
- mConfig.lastModifiedDate = Instant.now().getEpochSecond();
- try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
- PersistentSystemFontConfig.writeToXml(fos, mConfig);
+ mLastModifiedDate = Instant.now().getEpochSecond();
+ try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
+ PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+ } catch (Exception e) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
+ "Failed to write config XML.", e);
+ }
+ mConfigVersion++;
+ }
+
+ /**
+ * Applies multiple {@link FontUpdateRequest}s in transaction.
+ * If one of the request fails, the fonts and config are rolled back to the previous state
+ * before this method is called.
+ */
+ public void update(List<FontUpdateRequest> requests) throws SystemFontException {
+ // Backup the mapping for rollback.
+ HashMap<String, FontFileInfo> backupMap = new HashMap<>(mFontFileInfoMap);
+ long backupLastModifiedDate = mLastModifiedDate;
+ boolean success = false;
+ try {
+ for (FontUpdateRequest request : requests) {
+ installFontFile(request.getFd().getFileDescriptor(), request.getSignature());
+ }
+
+ // Write config file.
+ mLastModifiedDate = Instant.now().getEpochSecond();
+ try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
+ PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
"Failed to write config XML.", e);
}
+
+ if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
+ "Failed to stage the config file.");
+ }
mConfigVersion++;
+ success = true;
+ } finally {
+ if (!success) {
+ mFontFileInfoMap.clear();
+ mFontFileInfoMap.putAll(backupMap);
+ mLastModifiedDate = backupLastModifiedDate;
+ }
}
}
@@ -211,111 +250,79 @@
* @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file.
* @throws SystemFontException if error occurs.
*/
- void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws SystemFontException {
- synchronized (UpdatableFontDir.this) {
- File newDir = getRandomDir(mFilesDir);
- if (!newDir.mkdir()) {
+ private void installFontFile(FileDescriptor fd, byte[] pkcs7Signature)
+ throws SystemFontException {
+ File newDir = getRandomDir(mFilesDir);
+ if (!newDir.mkdir()) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+ "Failed to create font directory.");
+ }
+ try {
+ // Make newDir executable so that apps can access font file inside newDir.
+ Os.chmod(newDir.getAbsolutePath(), 0711);
+ } catch (ErrnoException e) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+ "Failed to change mode to 711", e);
+ }
+ boolean success = false;
+ try {
+ File tempNewFontFile = new File(newDir, "font.ttf");
+ try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) {
+ FileUtils.copy(fd, out.getFD());
+ } catch (IOException e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
- "Failed to create font directory.");
+ "Failed to write font file to storage.", e);
}
try {
- // Make newDir executable so that apps can access font file inside newDir.
- Os.chmod(newDir.getAbsolutePath(), 0711);
+ // Do not parse font file before setting up fs-verity.
+ // setUpFsverity throws IOException if failed.
+ mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
+ pkcs7Signature);
+ } catch (IOException e) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
+ "Failed to setup fs-verity.", e);
+ }
+ String postScriptName;
+ try {
+ postScriptName = mParser.getPostScriptName(tempNewFontFile);
+ } catch (IOException e) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_INVALID_FONT_FILE,
+ "Failed to read PostScript name from font file", e);
+ }
+ if (postScriptName == null) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_INVALID_FONT_NAME,
+ "Failed to read PostScript name from font file");
+ }
+ File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION);
+ if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+ "Failed to move verified font file.");
+ }
+ try {
+ // Make the font file readable by apps.
+ Os.chmod(newFontFile.getAbsolutePath(), 0644);
} catch (ErrnoException e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
"Failed to change mode to 711", e);
}
- boolean success = false;
- try {
- File tempNewFontFile = new File(newDir, "font.ttf");
- try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) {
- FileUtils.copy(fd, out.getFD());
- } catch (IOException e) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
- "Failed to write font file to storage.", e);
- }
- try {
- // Do not parse font file before setting up fs-verity.
- // setUpFsverity throws IOException if failed.
- mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
- pkcs7Signature);
- } catch (IOException e) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
- "Failed to setup fs-verity.", e);
- }
- String postScriptName;
- try {
- postScriptName = mParser.getPostScriptName(tempNewFontFile);
- } catch (IOException e) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_INVALID_FONT_FILE,
- "Failed to read PostScript name from font file", e);
- }
- if (postScriptName == null) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_INVALID_FONT_NAME,
- "Failed to read PostScript name from font file");
- }
- File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION);
- if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
- "Failed to move verified font file.");
- }
- try {
- // Make the font file readable by apps.
- Os.chmod(newFontFile.getAbsolutePath(), 0644);
- } catch (ErrnoException e) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
- "Failed to change mode to 711", e);
- }
- FontFileInfo fontFileInfo = validateFontFile(newFontFile);
-
- // Write config file.
- PersistentSystemFontConfig.Config copied = new PersistentSystemFontConfig.Config();
- mConfig.copyTo(copied);
-
- copied.lastModifiedDate = Instant.now().getEpochSecond();
- try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
- PersistentSystemFontConfig.writeToXml(fos, copied);
- } catch (Exception e) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
- "Failed to write config XML.", e);
- }
-
- // Backup the mapping for rollback.
- HashMap<String, FontFileInfo> backup = new HashMap<>(mFontFileInfoMap);
- if (!addFileToMapIfNewerLocked(fontFileInfo, false)) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_DOWNGRADING,
- "Downgrading font file is forbidden.");
- }
-
- if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) {
- // If we fail to stage the config file, need to rollback the config.
- mFontFileInfoMap.clear();
- mFontFileInfoMap.putAll(backup);
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
- "Failed to stage the config file.");
- }
-
-
- // Now font update is succeeded. Update config version.
- copied.copyTo(mConfig);
- mConfigVersion++;
-
- success = true;
- } finally {
- if (!success) {
- FileUtils.deleteContentsAndDir(newDir);
- }
+ FontFileInfo fontFileInfo = validateFontFile(newFontFile);
+ if (!addFileToMapIfNewer(fontFileInfo, false)) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_DOWNGRADING,
+ "Downgrading font file is forbidden.");
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ FileUtils.deleteContentsAndDir(newDir);
}
}
}
@@ -343,7 +350,7 @@
* higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link
* #mPreinstalledFontDirs}).
*/
- private boolean addFileToMapIfNewerLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) {
+ private boolean addFileToMapIfNewer(FontFileInfo fontFileInfo, boolean deleteOldFile) {
String name = fontFileInfo.getFile().getName();
FontFileInfo existingInfo = mFontFileInfoMap.get(name);
final boolean shouldAddToMap;
@@ -447,29 +454,28 @@
}
}
+ private PersistentSystemFontConfig.Config getPersistentConfig() {
+ PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+ config.lastModifiedDate = mLastModifiedDate;
+ for (FontFileInfo info : mFontFileInfoMap.values()) {
+ config.updatedFontDirs.add(info.getRandomizedFontDir().getName());
+ }
+ return config;
+ }
+
Map<String, File> getFontFileMap() {
Map<String, File> map = new HashMap<>();
- synchronized (UpdatableFontDir.this) {
- for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) {
- map.put(entry.getKey(), entry.getValue().getFile());
- }
+ for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) {
+ map.put(entry.getKey(), entry.getValue().getFile());
}
return map;
}
/* package */ FontConfig getSystemFontConfig() {
- synchronized (UpdatableFontDir.this) {
- return SystemFonts.getSystemFontConfig(
- getFontFileMap(),
- mConfig.lastModifiedDate,
- mConfigVersion
- );
- }
+ return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion);
}
/* package */ int getConfigVersion() {
- synchronized (UpdatableFontDir.this) {
- return mConfigVersion;
- }
+ return mConfigVersion;
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f6e08fb..2e4200c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -289,6 +289,8 @@
private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
private static native boolean nativeIsVibrating(long ptr, int deviceId);
private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
+ private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
+ private static native int nativeGetBatteryStatus(long ptr, int deviceId);
private static native void nativeReloadKeyboardLayouts(long ptr);
private static native void nativeReloadDeviceAliases(long ptr);
private static native String nativeDump(long ptr);
@@ -2008,6 +2010,18 @@
// Binder call
@Override
+ public int getBatteryStatus(int deviceId) {
+ return nativeGetBatteryStatus(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
+ public int getBatteryCapacity(int deviceId) {
+ return nativeGetBatteryCapacity(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
public void setPointerIconType(int iconId) {
nativeSetPointerIconType(mPtr, iconId);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6308ace..e5b5350 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4131,48 +4131,52 @@
@BinderThread
@Override
@GuardedBy("mMethodMap")
- public void startProtoDump(byte[] protoDump, int source, String where) {
- if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
- // Dump not triggered from IMMS, but no proto information provided.
- return;
- }
- ImeTracing tracingInstance = ImeTracing.getInstance();
- if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
- return;
- }
-
- ProtoOutputStream proto = new ProtoOutputStream();
- switch (source) {
- case ImeTracing.IME_TRACING_FROM_CLIENT:
- final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
- proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodClientsTraceProto.WHERE, where);
- proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
- proto.end(client_token);
- break;
- case ImeTracing.IME_TRACING_FROM_IMS:
- final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
- proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodServiceTraceProto.WHERE, where);
- proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
- proto.end(service_token);
- break;
- case IME_TRACING_FROM_IMMS:
- final long managerservice_token =
- proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
- proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
- dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
- proto.end(managerservice_token);
- break;
- default:
- // Dump triggered by a source not recognised.
+ public void startProtoDump(byte[] protoDump, int source, String where,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> {
+ if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
+ // Dump not triggered from IMMS, but no proto information provided.
return;
- }
- tracingInstance.addToBuffer(proto, source);
+ }
+ ImeTracing tracingInstance = ImeTracing.getInstance();
+ if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
+ return;
+ }
+
+ ProtoOutputStream proto = new ProtoOutputStream();
+ switch (source) {
+ case ImeTracing.IME_TRACING_FROM_CLIENT:
+ final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
+ proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
+ SystemClock.elapsedRealtimeNanos());
+ proto.write(InputMethodClientsTraceProto.WHERE, where);
+ proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
+ proto.end(client_token);
+ break;
+ case ImeTracing.IME_TRACING_FROM_IMS:
+ final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
+ proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
+ SystemClock.elapsedRealtimeNanos());
+ proto.write(InputMethodServiceTraceProto.WHERE, where);
+ proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
+ proto.end(service_token);
+ break;
+ case IME_TRACING_FROM_IMMS:
+ final long managerservice_token =
+ proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
+ proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
+ SystemClock.elapsedRealtimeNanos());
+ proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
+ dumpDebug(proto,
+ InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+ proto.end(managerservice_token);
+ break;
+ default:
+ // Dump triggered by a source not recognised.
+ return;
+ }
+ tracingInstance.addToBuffer(proto, source);
+ });
}
@BinderThread
@@ -4183,40 +4187,44 @@
@BinderThread
@Override
- public void startImeTrace() {
- ImeTracing.getInstance().startTrace(null /* printwriter */);
- ArrayMap<IBinder, ClientState> clients;
- synchronized (mMethodMap) {
- clients = new ArrayMap<>(mClients);
- }
- for (ClientState state : clients.values()) {
- if (state != null) {
- try {
- state.client.setImeTraceEnabled(true /* enabled */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+ public void startImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> {
+ ImeTracing.getInstance().startTrace(null /* printwriter */);
+ ArrayMap<IBinder, ClientState> clients;
+ synchronized (mMethodMap) {
+ clients = new ArrayMap<>(mClients);
+ }
+ for (ClientState state : clients.values()) {
+ if (state != null) {
+ try {
+ state.client.setImeTraceEnabled(true /* enabled */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+ }
}
}
- }
+ });
}
@BinderThread
@Override
- public void stopImeTrace() {
- ImeTracing.getInstance().stopTrace(null /* printwriter */);
- ArrayMap<IBinder, ClientState> clients;
- synchronized (mMethodMap) {
- clients = new ArrayMap<>(mClients);
- }
- for (ClientState state : clients.values()) {
- if (state != null) {
- try {
- state.client.setImeTraceEnabled(false /* enabled */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+ public void stopImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> {
+ ImeTracing.getInstance().stopTrace(null /* printwriter */);
+ ArrayMap<IBinder, ClientState> clients;
+ synchronized (mMethodMap) {
+ clients = new ArrayMap<>(mClients);
+ }
+ for (ClientState state : clients.values()) {
+ if (state != null) {
+ try {
+ state.client.setImeTraceEnabled(false /* enabled */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+ }
}
}
- }
+ });
}
@GuardedBy("mMethodMap")
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 7f9c766..6fec906 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1868,7 +1868,9 @@
@BinderThread
@Override
- public void startProtoDump(byte[] clientProtoDump, int source, String where) {
+ public void startProtoDump(byte[] clientProtoDump, int source, String where,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> { });
}
@BinderThread
@@ -1879,12 +1881,14 @@
@BinderThread
@Override
- public void startImeTrace() {
+ public void startImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> { });
}
@BinderThread
@Override
- public void stopImeTrace() {
+ public void stopImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> { });
}
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index d16267f..9216a6b 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -103,7 +103,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -1489,7 +1488,7 @@
}
if (locations.length > 0) {
- reportLocation(LocationResult.create(Arrays.asList(locations)).validate());
+ reportLocation(LocationResult.wrap(locations).validate());
}
for (Runnable listener : listeners) {
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 06ca9ec..221d4fb 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -20,8 +20,8 @@
import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.KEY_FLUSH_COMPLETE;
+import static android.location.LocationManager.KEY_LOCATIONS;
import static android.location.LocationManager.KEY_LOCATION_CHANGED;
-import static android.location.LocationManager.KEY_LOCATION_RESULT;
import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
@@ -187,7 +187,8 @@
@Override
public void deliverOnLocationChanged(LocationResult locationResult,
@Nullable Runnable onCompleteCallback) throws RemoteException {
- mListener.onLocationChanged(locationResult, SingleUseCallback.wrap(onCompleteCallback));
+ mListener.onLocationChanged(locationResult.asList(),
+ SingleUseCallback.wrap(onCompleteCallback));
}
@Override
@@ -222,12 +223,16 @@
// allows apps to start a fg service in response to a location PI
options.setTemporaryAppWhitelistDuration(TEMPORARY_APP_ALLOWLIST_DURATION_MS);
+ Intent intent = new Intent().putExtra(KEY_LOCATION_CHANGED,
+ locationResult.getLastLocation());
+ if (locationResult.size() > 1) {
+ intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
+ }
+
mPendingIntent.send(
mContext,
0,
- new Intent()
- .putExtra(KEY_LOCATION_CHANGED, locationResult.getLastLocation())
- .putExtra(KEY_LOCATION_RESULT, locationResult),
+ intent,
onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
: null,
null,
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index dce7b08..0d8f643 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -53,7 +53,7 @@
Location location = new Location(l);
location.setIsFromMockProvider(true);
mLocation = location;
- reportLocation(LocationResult.wrap(location));
+ reportLocation(LocationResult.wrap(location).validate());
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index c274c28..32d637f 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.location.Location;
import android.location.LocationResult;
import android.location.provider.ILocationProvider;
import android.location.provider.ILocationProviderManager;
@@ -40,6 +41,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -260,13 +262,25 @@
// executed on binder thread
@Override
- public void onReportLocation(LocationResult locationResult) {
+ public void onReportLocation(Location location) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
- reportLocation(locationResult.validate());
+ reportLocation(LocationResult.wrap(location).validate());
+ }
+ }
+
+ // executed on binder thread
+ @Override
+ public void onReportLocations(List<Location> locations) {
+ synchronized (mLock) {
+ if (mProxy != this) {
+ return;
+ }
+
+ reportLocation(LocationResult.wrap(locations).validate());
}
}
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index ea2788c..6d1c680 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -155,7 +155,7 @@
try {
// Use the privileged method because Lockdown VPN is initiated by the system, so
// no additional permission checks are necessary.
- mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, egressProp);
+ mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp);
} catch (IllegalStateException e) {
mAcceptedEgressIface = null;
Log.e(TAG, "Failed to start VPN", e);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 29eaf4f..ca21640 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -243,6 +243,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.StatLogger;
import com.android.internal.util.XmlUtils;
+import com.android.net.module.util.NetworkIdentityUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -1253,7 +1254,7 @@
// identified carrier, which may want to manage their own notifications. This method
// should be called every time the carrier config changes anyways, and there's no
// reason to alert if there isn't a carrier.
- return;
+ continue;
}
final boolean notifyWarning = getBooleanDefeatingNullable(config,
@@ -2163,13 +2164,14 @@
if (template.matches(probeIdent)) {
if (LOGD) {
Slog.d(TAG, "Found template " + template + " which matches subscriber "
- + NetworkIdentity.scrubSubscriberId(subscriberId));
+ + NetworkIdentityUtils.scrubSubscriberId(subscriberId));
}
return false;
}
}
- Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId)
+ Slog.i(TAG, "No policy for subscriber "
+ + NetworkIdentityUtils.scrubSubscriberId(subscriberId)
+ "; generating default policy");
final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId);
addNetworkPolicyAL(policy);
@@ -3614,14 +3616,15 @@
final int subId = mSubIdToSubscriberId.keyAt(i);
final String subscriberId = mSubIdToSubscriberId.valueAt(i);
- fout.println(subId + "=" + NetworkIdentity.scrubSubscriberId(subscriberId));
+ fout.println(subId + "="
+ + NetworkIdentityUtils.scrubSubscriberId(subscriberId));
}
fout.decreaseIndent();
fout.println();
for (String[] mergedSubscribers : mMergedSubscriberIds) {
fout.println("Merged subscriptions: " + Arrays.toString(
- NetworkIdentity.scrubSubscriberId(mergedSubscribers)));
+ NetworkIdentityUtils.scrubSubscriberIds(mergedSubscribers)));
}
fout.println();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8fa3a00..6843733 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -377,7 +377,8 @@
static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
Adjustment.KEY_CONTEXTUAL_ACTIONS,
- Adjustment.KEY_TEXT_REPLIES};
+ Adjustment.KEY_TEXT_REPLIES,
+ Adjustment.KEY_NOT_CONVERSATION};
static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
RoleManager.ROLE_DIALER,
@@ -2313,6 +2314,13 @@
} else if ("false".equals(value)) {
mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
}
+ } else if (SystemUiDeviceConfigFlags.ENABLE_NAS_NOT_CONVERSATION.equals(name)) {
+ String value = properties.getString(name, null);
+ if ("true".equals(value)) {
+ mAssistants.allowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+ } else if ("false".equals(value)) {
+ mAssistants.disallowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+ }
}
}
};
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 61c8b17..461d519 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1419,8 +1419,10 @@
conversation.setPkg(p.pkg);
conversation.setUid(p.uid);
conversation.setNotificationChannel(nc);
- conversation.setParentChannelLabel(
- p.channels.get(nc.getParentChannelId()).getName());
+ NotificationChannel parent = p.channels.get(nc.getParentChannelId());
+ conversation.setParentChannelLabel(parent == null
+ ? null
+ : parent.getName());
boolean blockedByGroup = false;
if (nc.getGroup() != null) {
NotificationChannelGroup group = p.groups.get(nc.getGroup());
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index f8990c0..5d7c41c 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -588,29 +588,32 @@
*
* @param recipientUid the uid gaining visibility of the {@code visibleUid}.
* @param visibleUid the uid becoming visible to the {@recipientUid}
+ * @return {@code true} if implicit access was not already granted.
*/
- public void grantImplicitAccess(int recipientUid, int visibleUid) {
- if (recipientUid != visibleUid) {
- final boolean changed = mImplicitlyQueryable.add(recipientUid, visibleUid);
- if (changed && DEBUG_LOGGING) {
- Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
- }
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- // update the cache in a one-off manner since we've got all the information we
- // need.
- SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid);
- if (visibleUids == null) {
- visibleUids = new SparseBooleanArray();
- mShouldFilterCache.put(recipientUid, visibleUids);
- }
- visibleUids.put(visibleUid, false);
+ public boolean grantImplicitAccess(int recipientUid, int visibleUid) {
+ if (recipientUid == visibleUid) {
+ return false;
+ }
+ final boolean changed = mImplicitlyQueryable.add(recipientUid, visibleUid);
+ if (changed && DEBUG_LOGGING) {
+ Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
+ }
+ synchronized (mCacheLock) {
+ if (mShouldFilterCache != null) {
+ // update the cache in a one-off manner since we've got all the information we
+ // need.
+ SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid);
+ if (visibleUids == null) {
+ visibleUids = new SparseBooleanArray();
+ mShouldFilterCache.put(recipientUid, visibleUids);
}
- }
- if (changed) {
- onChanged();
+ visibleUids.put(visibleUid, false);
}
}
+ if (changed) {
+ onChanged();
+ }
+ return changed;
}
public void onSystemReady() {
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 4f986bd..2a1fc87 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -33,7 +33,7 @@
public static final int DUMP_KEYSETS = 1 << 14;
public static final int DUMP_VERSION = 1 << 15;
public static final int DUMP_INSTALLS = 1 << 16;
- public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+ public static final int DUMP_DOMAIN_VERIFIER = 1 << 17;
public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
public static final int DUMP_FROZEN = 1 << 19;
public static final int DUMP_DEXOPT = 1 << 20;
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index c3bca28..15e1d52 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -470,23 +470,34 @@
return instantGrantList.get(instantAppId);
}
+ /**
+ * Allows an app to see an instant app.
+ *
+ * @param userId the userId in which this access is being granted
+ * @param intent when provided, this serves as the intent that caused
+ * this access to be granted
+ * @param recipientUid the uid of the app receiving visibility
+ * @param instantAppId the app ID of the instant app being made visible
+ * to the recipient
+ * @return {@code true} if access is granted.
+ */
@GuardedBy("mService.mLock")
- public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
+ public boolean grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
int recipientUid, int instantAppId) {
if (mInstalledInstantAppUids == null) {
- return; // no instant apps installed; no need to grant
+ return false; // no instant apps installed; no need to grant
}
WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
if (instantAppList == null || !instantAppList.get(instantAppId)) {
- return; // instant app id isn't installed; no need to grant
+ return false; // instant app id isn't installed; no need to grant
}
if (instantAppList.get(recipientUid)) {
- return; // target app id is an instant app; no need to grant
+ return false; // target app id is an instant app; no need to grant
}
if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
final Set<String> categories = intent.getCategories();
if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
- return; // launched via VIEW/BROWSABLE intent; no need to grant
+ return false; // launched via VIEW/BROWSABLE intent; no need to grant
}
}
WatchedSparseArray<WatchedSparseBooleanArray> targetAppList = mInstantGrants.get(userId);
@@ -500,6 +511,7 @@
targetAppList.put(recipientUid, instantGrantList);
}
instantGrantList.put(instantAppId, true /*granted*/);
+ return true;
}
@GuardedBy("mService.mLock")
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 13fe8a0..6485c0c 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
@@ -407,8 +408,14 @@
boolean needsRenderScriptOverride = false;
if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null
&& NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
- abiList = Build.SUPPORTED_32_BIT_ABIS;
- needsRenderScriptOverride = true;
+ if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+ abiList = Build.SUPPORTED_32_BIT_ABIS;
+ needsRenderScriptOverride = true;
+ } else {
+ throw new PackageManagerException(
+ INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
+ "Apks with renderscript are not supported on 64-bit only devices");
+ }
}
final int copyRet;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f68113d..a702f5e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -377,6 +377,11 @@
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.dex.ViewCompiler;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationService;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
@@ -399,6 +404,7 @@
import com.android.server.utils.Watched;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedSparseBooleanArray;
+import com.android.server.utils.WatchedSparseIntArray;
import com.android.server.utils.Watcher;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -460,6 +466,7 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.function.Supplier;
/**
* Keep track of all those APKs everywhere.
@@ -1063,6 +1070,9 @@
private final ServiceProducer mGetLocalServiceProducer;
private final ServiceProducer mGetSystemServiceProducer;
private final Singleton<ModuleInfoProvider> mModuleInfoProviderProducer;
+ private final Singleton<DomainVerificationManagerInternal>
+ mDomainVerificationManagerInternalProducer;
+ private final Singleton<Handler> mHandlerProducer;
Injector(Context context, Object lock, Installer installer,
Object installLock, PackageAbiHelper abiHelper,
@@ -1091,6 +1101,9 @@
instantAppResolverConnectionProducer,
Producer<ModuleInfoProvider> moduleInfoProviderProducer,
Producer<LegacyPermissionManagerInternal> legacyPermissionManagerInternalProducer,
+ Producer<DomainVerificationManagerInternal>
+ domainVerificationManagerInternalProducer,
+ Producer<Handler> handlerProducer,
SystemWrapper systemWrapper,
ServiceProducer getLocalServiceProducer,
ServiceProducer getSystemServiceProducer) {
@@ -1128,6 +1141,9 @@
mSystemWrapper = systemWrapper;
mGetLocalServiceProducer = getLocalServiceProducer;
mGetSystemServiceProducer = getSystemServiceProducer;
+ mDomainVerificationManagerInternalProducer =
+ new Singleton<>(domainVerificationManagerInternalProducer);
+ mHandlerProducer = new Singleton<>(handlerProducer);
}
/**
@@ -1273,6 +1289,14 @@
public LegacyPermissionManagerInternal getLegacyPermissionManagerInternal() {
return mLegacyPermissionManagerInternalProducer.get(this, mPackageManager);
}
+
+ public DomainVerificationManagerInternal getDomainVerificationManagerInternal() {
+ return mDomainVerificationManagerInternalProducer.get(this, mPackageManager);
+ }
+
+ public Handler getHandler() {
+ return mHandlerProducer.get(this, mPackageManager);
+ }
}
/** Provides an abstraction to static access to system state. */
@@ -1327,8 +1351,6 @@
public InstantAppRegistry instantAppRegistry;
public InstantAppResolverConnection instantAppResolverConnection;
public ComponentName instantAppResolverSettingsComponent;
- public @Nullable IntentFilterVerifier<ParsedIntentInfo> intentFilterVerifier;
- public @Nullable ComponentName intentFilterVerifierComponent;
public boolean isPreNmr1Upgrade;
public boolean isPreNupgrade;
public boolean isPreQupgrade;
@@ -1451,10 +1473,8 @@
boolean mResolverReplaced = false;
- private final @Nullable ComponentName mIntentFilterVerifierComponent;
- private final @Nullable IntentFilterVerifier<ParsedIntentInfo> mIntentFilterVerifier;
-
- private int mIntentFilterVerificationToken = 0;
+ @NonNull
+ private final DomainVerificationManagerInternal mDomainVerificationManager;
/** The service connection to the ephemeral resolver */
final InstantAppResolverConnection mInstantAppResolverConnection;
@@ -1470,9 +1490,6 @@
private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
- final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
- = new SparseArray<>();
-
// Internal interface for permission manager
private final PermissionManagerServiceInternal mPermissionManager;
@@ -1492,262 +1509,6 @@
private final PackageProperty mPackageProperty = new PackageProperty();
- private static class IFVerificationParams {
- String packageName;
- boolean hasDomainUrls;
- List<ParsedActivity> activities;
- boolean replacing;
- int userId;
- int verifierUid;
-
- public IFVerificationParams(String packageName, boolean hasDomainUrls,
- List<ParsedActivity> activities, boolean _replacing,
- int _userId, int _verifierUid) {
- this.packageName = packageName;
- this.hasDomainUrls = hasDomainUrls;
- this.activities = activities;
- replacing = _replacing;
- userId = _userId;
- verifierUid = _verifierUid;
- }
- }
-
- private interface IntentFilterVerifier<T extends IntentFilter> {
- boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
- T filter, String packageName);
- void startVerifications(int userId);
- void receiveVerificationResponse(int verificationId);
- }
-
- private class IntentVerifierProxy implements IntentFilterVerifier<ParsedIntentInfo> {
- private Context mContext;
- private ComponentName mIntentFilterVerifierComponent;
- private ArrayList<Integer> mCurrentIntentFilterVerifications = new ArrayList<>();
-
- public IntentVerifierProxy(Context context, ComponentName verifierComponent) {
- mContext = context;
- mIntentFilterVerifierComponent = verifierComponent;
- }
-
- private String getDefaultScheme() {
- return IntentFilter.SCHEME_HTTPS;
- }
-
- @Override
- public void startVerifications(int userId) {
- // Launch verifications requests
- int count = mCurrentIntentFilterVerifications.size();
- for (int n=0; n<count; n++) {
- int verificationId = mCurrentIntentFilterVerifications.get(n);
- final IntentFilterVerificationState ivs =
- mIntentFilterVerificationStates.get(verificationId);
-
- String packageName = ivs.getPackageName();
-
- ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
- final int filterCount = filters.size();
- ArraySet<String> domainsSet = new ArraySet<>();
- for (int m=0; m<filterCount; m++) {
- ParsedIntentInfo filter = filters.get(m);
- domainsSet.addAll(filter.getHostsList());
- }
- synchronized (mLock) {
- if (mSettings.createIntentFilterVerificationIfNeededLPw(
- packageName, domainsSet) != null) {
- scheduleWriteSettingsLocked();
- }
- }
- sendVerificationRequest(verificationId, ivs);
- }
- mCurrentIntentFilterVerifications.clear();
- }
-
- private void sendVerificationRequest(int verificationId, IntentFilterVerificationState ivs) {
- Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
- verificationId);
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
- getDefaultScheme());
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
- ivs.getHostsString());
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
- ivs.getPackageName());
- verificationIntent.setComponent(mIntentFilterVerifierComponent);
- verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-
- final long whitelistTimeout = getVerificationTimeout();
- final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setTemporaryAppWhitelistDuration(whitelistTimeout);
-
- DeviceIdleInternal idleController =
- mInjector.getLocalService(DeviceIdleInternal.class);
- idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
- mIntentFilterVerifierComponent.getPackageName(), whitelistTimeout,
- UserHandle.USER_SYSTEM, true, "intent filter verifier");
-
- mContext.sendBroadcastAsUser(verificationIntent, UserHandle.SYSTEM,
- null, options.toBundle());
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "Sending IntentFilter verification broadcast");
- }
-
- public void receiveVerificationResponse(int verificationId) {
- IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
-
- final boolean verified = ivs.isVerified();
-
- ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
- final int count = filters.size();
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Received verification response " + verificationId
- + " for " + count + " filters, verified=" + verified);
- }
- for (int n=0; n<count; n++) {
- ParsedIntentInfo filter = filters.get(n);
- filter.setVerified(verified);
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "IntentFilter " + filter.toString()
- + " verified with result:" + verified + " and hosts:"
- + ivs.getHostsString());
- }
-
- mIntentFilterVerificationStates.remove(verificationId);
-
- final String packageName = ivs.getPackageName();
- IntentFilterVerificationInfo ivi;
-
- synchronized (mLock) {
- ivi = mSettings.getIntentFilterVerificationLPr(packageName);
- }
- if (ivi == null) {
- Slog.w(TAG, "IntentFilterVerificationInfo not found for verificationId:"
- + verificationId + " packageName:" + packageName);
- return;
- }
-
- synchronized (mLock) {
- if (verified) {
- ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
- } else {
- ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK);
- }
- scheduleWriteSettingsLocked();
-
- final int userId = ivs.getUserId();
- if (userId != UserHandle.USER_ALL) {
- final int userStatus =
- mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
-
- int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- boolean needUpdate = false;
-
- // In a success case, we promote from undefined or ASK to ALWAYS. This
- // supports a flow where the app fails validation but then ships an updated
- // APK that passes, and therefore deserves to be in ALWAYS.
- //
- // If validation failed, the undefined state winds up in the basic ASK behavior,
- // but apps that previously passed and became ALWAYS are *demoted* out of
- // that state, since they would not deserve the ALWAYS behavior in case of a
- // clean install.
- switch (userStatus) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
- if (!verified) {
- // Don't demote if sysconfig says 'always'
- SystemConfig systemConfig = mInjector.getSystemConfig();
- ArraySet<String> packages = systemConfig.getLinkedApps();
- if (!packages.contains(packageName)) {
- // updatedStatus is already UNDEFINED
- needUpdate = true;
-
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Formerly validated but now failing; demoting");
- }
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Updating bundled package " + packageName
- + " failed autoVerify, but sysconfig supersedes");
- }
- // leave needUpdate == false here intentionally
- }
- }
- break;
-
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
- // Stay in 'undefined' on verification failure
- if (verified) {
- updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- }
- needUpdate = true;
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Applying update; old=" + userStatus
- + " new=" + updatedStatus);
- }
- break;
-
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
- // Keep in 'ask' on failure
- if (verified) {
- updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- needUpdate = true;
- }
- break;
-
- default:
- // Nothing to do
- }
-
- if (needUpdate) {
- mSettings.updateIntentFilterVerificationStatusLPw(
- packageName, updatedStatus, userId);
- scheduleWritePackageRestrictionsLocked(userId);
- }
- } else {
- Slog.i(TAG, "autoVerify ignored when installing for all users");
- }
- }
- }
-
- @Override
- public boolean addOneIntentFilterVerification(int verifierUid, int userId, int verificationId,
- ParsedIntentInfo filter, String packageName) {
- if (!hasValidDomains(filter)) {
- return false;
- }
- IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
- if (ivs == null) {
- ivs = createDomainVerificationState(verifierUid, userId, verificationId,
- packageName);
- }
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Adding verification filter for " + packageName + ": " + filter);
- }
- ivs.addFilter(filter);
- return true;
- }
-
- private IntentFilterVerificationState createDomainVerificationState(int verifierUid,
- int userId, int verificationId, String packageName) {
- IntentFilterVerificationState ivs = new IntentFilterVerificationState(
- verifierUid, userId, packageName);
- ivs.setPendingState();
- synchronized (mLock) {
- mIntentFilterVerificationStates.append(verificationId, ivs);
- mCurrentIntentFilterVerifications.add(verificationId);
- }
- return ivs;
- }
- }
-
- private static boolean hasValidDomains(ParsedIntentInfo filter) {
- return filter.hasCategory(Intent.CATEGORY_BROWSABLE)
- && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
- filter.hasDataScheme(IntentFilter.SCHEME_HTTPS));
- }
-
// Set of pending broadcasts for aggregating enable/disable of components.
@VisibleForTesting(visibility = Visibility.PACKAGE)
public static class PendingPackageBroadcasts {
@@ -1822,8 +1583,8 @@
static final int WRITE_PACKAGE_RESTRICTIONS = 14;
static final int PACKAGE_VERIFIED = 15;
static final int CHECK_PENDING_VERIFICATION = 16;
- static final int START_INTENT_FILTER_VERIFICATIONS = 17;
- static final int INTENT_FILTER_VERIFIED = 18;
+ // public static final int UNUSED = 17;
+ // public static final int UNUSED = 18;
static final int WRITE_PACKAGE_LIST = 19;
static final int INSTANT_APP_RESOLUTION_PHASE_TWO = 20;
static final int ENABLE_ROLLBACK_STATUS = 21;
@@ -1832,6 +1593,7 @@
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24;
static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
+ static final int DOMAIN_VERIFICATION = 27;
static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
@@ -1925,6 +1687,74 @@
private final PackageUsage mPackageUsage = new PackageUsage();
private final CompilerStats mCompilerStats = new CompilerStats();
+ private final DomainVerificationConnection mDomainVerificationConnection =
+ new DomainVerificationConnection();
+
+ private class DomainVerificationConnection implements
+ DomainVerificationService.Connection, DomainVerificationProxyV1.Connection,
+ DomainVerificationProxyV2.Connection {
+
+ @Override
+ public void scheduleWriteSettings() {
+ synchronized (mLock) {
+ PackageManagerService.this.scheduleWriteSettingsLocked();
+ }
+ }
+
+ @Override
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ @UserIdInt
+ @Override
+ public int getCallingUserId() {
+ return UserHandle.getCallingUserId();
+ }
+
+ @Override
+ public void schedule(int code, @Nullable Object object) {
+ Message message = mHandler.obtainMessage(DOMAIN_VERIFICATION);
+ message.arg1 = code;
+ message.obj = object;
+ mHandler.sendMessage(message);
+ }
+
+ @Override
+ public long getPowerSaveTempWhitelistAppDuration() {
+ return PackageManagerService.this.getVerificationTimeout();
+ }
+
+ @Override
+ public DeviceIdleInternal getDeviceIdleInternal() {
+ return mInjector.getLocalService(DeviceIdleInternal.class);
+ }
+
+ @Override
+ public boolean isCallerPackage(int callingUid, @NonNull String packageName) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ return callingUid == getPackageUid(packageName, 0, callingUserId);
+ }
+
+ @Nullable
+ @Override
+ public PackageSetting getPackageSettingLocked(@NonNull String pkgName) {
+ return PackageManagerService.this.getPackageSetting(pkgName);
+ }
+
+ @Nullable
+ @Override
+ public AndroidPackage getPackageLocked(@NonNull String pkgName) {
+ return PackageManagerService.this.getPackage(pkgName);
+ }
+
+ @Nullable
+ @Override
+ public AndroidPackage getPackage(@NonNull String packageName) {
+ return getPackageLocked(packageName);
+ }
+ }
+
/**
* Invalidate the package info cache, which includes updating the cached computer.
* @hide
@@ -2145,7 +1975,6 @@
boolean isImplicitImageCaptureIntentAndNotSetByDpc);
int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps,
boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc);
- long getDomainVerificationStatusLPr(PackageSetting ps, int userId);
void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell, String message);
void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
@@ -2199,6 +2028,7 @@
private final ComponentResolver mComponentResolver;
private final InstantAppResolverConnection mInstantAppResolverConnection;
private final DefaultAppProvider mDefaultAppProvider;
+ private final DomainVerificationManagerInternal mDomainVerificationManager;
// PackageManagerService attributes that are primitives are referenced through the
// pms object directly. Primitives are the only attributes so referenced.
@@ -2244,6 +2074,7 @@
mComponentResolver = args.service.mComponentResolver;
mInstantAppResolverConnection = args.service.mInstantAppResolverConnection;
mDefaultAppProvider = args.service.mDefaultAppProvider;
+ mDomainVerificationManager = args.service.mDomainVerificationManager;
// Used to reference PMS attributes that are primitives and which are not
// updated under control of the PMS lock.
@@ -2755,8 +2586,6 @@
final ArrayList<ResolveInfo> result = new ArrayList<>();
final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
- final ArrayList<ResolveInfo> alwaysAskList = new ArrayList<>();
- final ArrayList<ResolveInfo> neverList = new ArrayList<>();
final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
final int count = candidates.size();
// First, try to use linked apps. Partition the candidates into four lists:
@@ -2772,43 +2601,15 @@
matchAllList.add(info);
continue;
}
- // Try to get the status from User settings first
- long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- int status = (int)(packedStatus >> 32);
- int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + always: " + info.activityInfo.packageName
- + " : linkgen=" + linkGeneration);
- }
- if (!intent.hasCategory(CATEGORY_BROWSABLE)
- || !intent.hasCategory(CATEGORY_DEFAULT)) {
- undefinedList.add(info);
- continue;
- }
-
- // Use link-enabled generation as preferredOrder, i.e.
- // prefer newly-enabled over earlier-enabled.
- info.preferredOrder = linkGeneration;
+ boolean isAlways = mDomainVerificationManager
+ .isApprovedForDomain(ps, intent, userId);
+ if (isAlways) {
alwaysList.add(info);
- } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + never: " + info.activityInfo.packageName);
- }
- neverList.add(info);
- } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + always-ask: " + info.activityInfo.packageName);
- }
- alwaysAskList.add(info);
- } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
- status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + ask: " + info.activityInfo.packageName);
- }
+ } else {
undefinedList.add(info);
}
+ continue;
}
}
@@ -2822,25 +2623,12 @@
// Add all undefined apps as we want them to appear in the disambiguation dialog.
result.addAll(undefinedList);
// Maybe add one for the other profile.
- if (xpDomainInfo != null && (
- xpDomainInfo.bestDomainVerificationStatus
- != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
+ if (xpDomainInfo != null && xpDomainInfo.wereAnyDomainsVerificationApproved) {
result.add(xpDomainInfo.resolveInfo);
}
includeBrowser = true;
}
- // The presence of any 'always ask' alternatives means we'll also offer browsers.
- // If there were 'always' entries their preferred order has been set, so we also
- // back that off to make the alternatives equivalent
- if (alwaysAskList.size() > 0) {
- for (ResolveInfo i : result) {
- i.preferredOrder = 0;
- }
- result.addAll(alwaysAskList);
- includeBrowser = true;
- }
-
if (includeBrowser) {
// Also add browsers (all of them or only the default one)
if (DEBUG_DOMAIN_VERIFICATION) {
@@ -2891,7 +2679,6 @@
// has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
if (result.size() == 0) {
result.addAll(candidates);
- result.removeAll(neverList);
}
}
return result;
@@ -2985,21 +2772,16 @@
if (ps == null) {
continue;
}
- long verificationState = getDomainVerificationStatusLPr(ps, parentUserId);
- int status = (int)(verificationState >> 32);
if (result == null) {
result = new CrossProfileDomainInfo();
result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(),
sourceUserId, parentUserId);
- result.bestDomainVerificationStatus = status;
- } else {
- result.bestDomainVerificationStatus = bestDomainVerificationStatus(status,
- result.bestDomainVerificationStatus);
}
+
+ result.wereAnyDomainsVerificationApproved |= mDomainVerificationManager
+ .isApprovedForDomain(ps, intent, riTargetUser.targetUserId);
}
- // Don't consider matches with status NEVER across profiles.
- if (result != null && result.bestDomainVerificationStatus
- == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ if (result != null && !result.wereAnyDomainsVerificationApproved) {
return null;
}
return result;
@@ -3242,26 +3024,20 @@
final String packageName = info.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps.getInstantApp(userId)) {
- final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- final int status = (int)(packedStatus >> 32);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
- // there's a local instant application installed, but, the user has
- // chosen to never use it; skip resolution and don't acknowledge
- // an instant application is even available
+ if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName);
- }
- blockResolution = true;
- break;
- } else {
- // we have a locally installed instant application; skip resolution
- // but acknowledge there's an instant application available
- if (DEBUG_INSTANT) {
- Slog.v(TAG, "Found installed instant app; pkg: " + packageName);
+ Slog.v(TAG, "Instant app approvd for intent; pkg: "
+ + packageName);
}
localInstantApp = info;
- break;
+ } else {
+ if (DEBUG_INSTANT) {
+ Slog.v(TAG, "Instant app not approved for intent; pkg: "
+ + packageName);
+ }
+ blockResolution = true;
}
+ break;
}
}
}
@@ -3863,7 +3639,8 @@
int i = 0;
for (int index = 0; index < N; index++) {
final PackageSetting ps = sus.packages.valueAt(index);
- if (ps.getInstalled(userId)) {
+ if (ps.getInstalled(userId)
+ && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
res[i++] = ps.name;
}
}
@@ -4174,14 +3951,10 @@
if (ps != null) {
// only check domain verification status if the app is not a browser
if (!info.handleAllWebDataURI) {
- // Try to get the status from User settings first
- final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- final int status = (int) (packedStatus >> 32);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
- || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+ if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "DENY instant app;"
- + " pkg: " + packageName + ", status: " + status);
+ Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
+ + ", approved");
}
return false;
}
@@ -4474,21 +4247,6 @@
return updateFlagsForComponent(flags, userId);
}
- // Returns a packed value as a long:
- //
- // high 'int'-sized word: link status: undefined/ask/never/always.
- // low 'int'-sized word: relative priority among 'always' results.
- public long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
- long result = ps.getDomainVerificationStatusForUser(userId);
- // if none available, get the status
- if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
- if (ps.getIntentFilterVerificationInfo() != null) {
- result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
- }
- }
- return result;
- }
-
/**
* Checks if the request is from the system or an app that has the appropriate cross-user
* permissions defined as follows:
@@ -5338,55 +5096,6 @@
params.handleIntegrityVerificationFinished();
break;
}
- case START_INTENT_FILTER_VERIFICATIONS: {
- IFVerificationParams params = (IFVerificationParams) msg.obj;
- verifyIntentFiltersIfNeeded(params.userId, params.verifierUid, params.replacing,
- params.packageName, params.hasDomainUrls, params.activities);
- break;
- }
- case INTENT_FILTER_VERIFIED: {
- final int verificationId = msg.arg1;
-
- final IntentFilterVerificationState state = mIntentFilterVerificationStates.get(
- verificationId);
- if (state == null) {
- Slog.w(TAG, "Invalid IntentFilter verification token "
- + verificationId + " received");
- break;
- }
-
- final int userId = state.getUserId();
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "Processing IntentFilter verification with token:"
- + verificationId + " and userId:" + userId);
-
- final IntentFilterVerificationResponse response =
- (IntentFilterVerificationResponse) msg.obj;
-
- state.setVerifierResponse(response.callerUid, response.code);
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "IntentFilter verification with token:" + verificationId
- + " and userId:" + userId
- + " is settings verifier response with response code:"
- + response.code);
-
- if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Domains failing verification: "
- + response.getFailedDomainsString());
- }
-
- if (state.isVerificationComplete()) {
- mIntentFilterVerifier.receiveVerificationResponse(verificationId);
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "IntentFilter verification with token:" + verificationId
- + " was not said to be complete");
- }
-
- break;
- }
case INSTANT_APP_RESOLUTION_PHASE_TWO: {
InstantAppResolver.doInstantAppResolutionPhaseTwo(mContext,
mInstantAppResolverConnection,
@@ -5447,6 +5156,12 @@
}
break;
}
+ case DOMAIN_VERIFICATION: {
+ int messageCode = msg.arg1;
+ Object object = msg.obj;
+ mDomainVerificationManager.runMessage(messageCode, object);
+ break;
+ }
}
}
}
@@ -6013,7 +5728,8 @@
}
public static PackageManagerService main(Context context, Installer installer,
- boolean factoryTest, boolean onlyCore) {
+ @NonNull DomainVerificationService domainVerificationService, boolean factoryTest,
+ boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
@@ -6036,7 +5752,8 @@
lock),
(i, pm) -> new Settings(Environment.getDataDirectory(),
RuntimePermissionsPersistence.createInstance(),
- i.getPermissionManagerServiceInternal(), lock),
+ i.getPermissionManagerServiceInternal(),
+ domainVerificationService, lock),
(i, pm) -> AppsFilter.create(pm.mPmInternal, i),
(i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
(i, pm) -> SystemConfig.getInstance(),
@@ -6069,6 +5786,13 @@
i.getContext(), cn, Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE),
(i, pm) -> new ModuleInfoProvider(i.getContext(), pm),
(i, pm) -> LegacyPermissionManagerService.create(i.getContext()),
+ (i, pm) -> domainVerificationService,
+ (i, pm) -> {
+ HandlerThread thread = new ServiceThread(TAG,
+ Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+ thread.start();
+ return pm.new PackageHandler(thread.getLooper());
+ },
new DefaultSystemWrapper(),
LocalServices::getService,
context::getSystemService);
@@ -6240,6 +5964,9 @@
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();
mUserManager = injector.getUserManagerService();
+ mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
+ mHandler = injector.getHandler();
+
mApexManager = testParams.apexManager;
mArtManagerService = testParams.artManagerService;
mAvailableFeatures = testParams.availableFeatures;
@@ -6249,14 +5976,11 @@
mDexManager = testParams.dexManager;
mDirsToScanAsSystem = testParams.dirsToScanAsSystem;
mFactoryTest = testParams.factoryTest;
- mHandler = testParams.handler;
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
mInstantAppRegistry = testParams.instantAppRegistry;
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
- mIntentFilterVerifier = testParams.intentFilterVerifier;
- mIntentFilterVerifierComponent = testParams.intentFilterVerifierComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
mIsPreNUpgrade = testParams.isPreNupgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
@@ -6462,6 +6186,9 @@
mAppInstallDir = new File(Environment.getDataDirectory(), "app");
mAppLib32InstallDir = getAppLib32InstallDir();
+ mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
+ mDomainVerificationManager.setConnection(mDomainVerificationConnection);
+
// Link up the watchers
mPackages.registerObserver(mWatcher);
mSharedLibraries.registerObserver(mWatcher);
@@ -6484,10 +6211,7 @@
synchronized (mInstallLock) {
// writer
synchronized (mLock) {
- HandlerThread handlerThread = new ServiceThread(TAG,
- Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
- handlerThread.start();
- mHandler = new PackageHandler(handlerThread.getLooper());
+ mHandler = injector.getHandler();
mProcessLoggingHandler = new ProcessLoggingHandler();
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
@@ -6972,7 +6696,6 @@
if (!mOnlyCore && (mPromoteSystemApps || mFirstBoot)) {
for (UserInfo user : mInjector.getUserManagerInternal().getUsers(true)) {
mSettings.applyDefaultPreferredAppsLPw(user.id);
- primeDomainVerificationsLPw(user.id);
}
}
@@ -7079,13 +6802,18 @@
mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr();
mRequiredInstallerPackage = getRequiredInstallerLPr();
mRequiredUninstallerPackage = getRequiredUninstallerLPr();
- mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
- if (mIntentFilterVerifierComponent != null) {
- mIntentFilterVerifier = new IntentVerifierProxy(mContext,
- mIntentFilterVerifierComponent);
- } else {
- mIntentFilterVerifier = null;
- }
+ ComponentName intentFilterVerifierComponent =
+ getIntentFilterVerifierComponentNameLPr();
+ ComponentName domainVerificationAgent =
+ getDomainVerificationAgentComponentNameLPr();
+
+ DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
+ intentFilterVerifierComponent, domainVerificationAgent, mContext,
+ mDomainVerificationManager, mDomainVerificationManager.getCollector(),
+ mDomainVerificationConnection);
+
+ mDomainVerificationManager.setProxy(domainVerificationProxy);
+
mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr();
mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
@@ -7094,8 +6822,6 @@
mRequiredVerifierPackage = null;
mRequiredInstallerPackage = null;
mRequiredUninstallerPackage = null;
- mIntentFilterVerifierComponent = null;
- mIntentFilterVerifier = null;
mServicesExtensionPackageName = null;
mSharedSystemSharedLibraryPackageName = null;
}
@@ -7630,6 +7356,40 @@
return null;
}
+ @Nullable
+ private ComponentName getDomainVerificationAgentComponentNameLPr() {
+ Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
+ List<ResolveInfo> matches = queryIntentReceiversInternal(intent, null,
+ MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+ ResolveInfo best = null;
+ final int N = matches.size();
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo cur = matches.get(i);
+ final String packageName = cur.getComponentInfo().packageName;
+ if (checkPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+ packageName, UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Domain verification agent found but does not hold permission: "
+ + packageName);
+ continue;
+ }
+
+ if (best == null || cur.priority > best.priority) {
+ if (cur.getComponentInfo().enabled) {
+ best = cur;
+ } else {
+ Slog.w(TAG, "Domain verification agent found but not enabled");
+ }
+ }
+ }
+
+ if (best != null) {
+ return best.getComponentInfo().getComponentName();
+ }
+ Slog.w(TAG, "Domain verification agent not found");
+ return null;
+ }
+
@Override
public @Nullable ComponentName getInstantAppResolverComponent() {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
@@ -7763,60 +7523,6 @@
return matches.get(0).getComponentInfo().getComponentName();
}
- @GuardedBy("mLock")
- private void primeDomainVerificationsLPw(int userId) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Priming domain verifications in user " + userId);
- }
-
- SystemConfig systemConfig = mInjector.getSystemConfig();
- ArraySet<String> packages = systemConfig.getLinkedApps();
-
- for (String packageName : packages) {
- AndroidPackage pkg = mPackages.get(packageName);
- if (pkg != null) {
- if (!pkg.isSystem()) {
- Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>");
- continue;
- }
-
- ArraySet<String> domains = null;
- for (ParsedActivity a : pkg.getActivities()) {
- for (ParsedIntentInfo filter : a.getIntents()) {
- if (hasValidDomains(filter)) {
- if (domains == null) {
- domains = new ArraySet<>();
- }
- domains.addAll(filter.getHostsList());
- }
- }
- }
-
- if (domains != null && domains.size() > 0) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.v(TAG, " + " + packageName);
- }
- // 'Undefined' in the global IntentFilterVerificationInfo, i.e. the usual
- // state w.r.t. the formal app-linkage "no verification attempted" state;
- // and then 'always' in the per-user state actually used for intent resolution.
- final IntentFilterVerificationInfo ivi;
- ivi = mSettings.createIntentFilterVerificationIfNeededLPw(packageName, domains);
- ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
- mSettings.updateIntentFilterVerificationStatusLPw(packageName,
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, userId);
- } else {
- Slog.w(TAG, "Sysconfig <app-link> package '" + packageName
- + "' does not handle web links");
- }
- } else {
- Slog.w(TAG, "Unknown package " + packageName + " in sysconfig <app-link>");
- }
- }
-
- scheduleWritePackageRestrictionsLocked(userId);
- scheduleWriteSettingsLocked();
- }
-
private boolean packageIsBrowser(String packageName, int userId) {
List<ResolveInfo> list = queryIntentActivitiesInternal(sBrowserIntent, null,
PackageManager.MATCH_ALL, userId);
@@ -9662,9 +9368,8 @@
if (ri.activityInfo.applicationInfo.isInstantApp()) {
final String packageName = ri.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
- final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- final int status = (int)(packedStatus >> 32);
- if (status != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+ if (ps != null && mDomainVerificationManager
+ .isApprovedForDomain(ps, intent, userId)) {
return ri;
}
}
@@ -10156,8 +9861,7 @@
private static class CrossProfileDomainInfo {
/* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
ResolveInfo resolveInfo;
- /* Best domain verification status of the activities found in the other profile */
- int bestDomainVerificationStatus;
+ boolean wereAnyDomainsVerificationApproved;
}
private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
@@ -10243,13 +9947,6 @@
xpDomainInfo, userId, debug);
}
- // Returns a packed value as a long:
- //
- // high 'int'-sized word: link status: undefined/ask/never/always.
- // low 'int'-sized word: relative priority among 'always' results.
- private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
- return liveComputer().getDomainVerificationStatusLPr(ps, userId);
- }
private ResolveInfo querySkipCurrentProfileIntents(
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
@@ -13499,7 +13196,7 @@
final int userId = user == null ? 0 : user.getIdentifier();
// Modify state for the given package setting
- commitPackageSettings(pkg, oldPkg, pkgSetting, scanFlags,
+ commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
(parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
if (pkgSetting.getInstantApp(userId)) {
mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
@@ -13756,6 +13453,9 @@
usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
}
+
+ final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();
+
// TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
// to avoid adding something that's unsupported due to lack of state, since it's called
// with null.
@@ -13779,7 +13479,8 @@
parsedPackage.getVersionCode(), pkgFlags, pkgPrivateFlags, user,
true /*allowInstall*/, instantApp, virtualPreload,
UserManagerService.getInstance(), usesStaticLibraries,
- parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups());
+ parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
+ newDomainSetId);
} else {
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
@@ -13798,7 +13499,7 @@
PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
UserManagerService.getInstance(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups());
+ parsedPackage.getMimeGroups(), newDomainSetId);
}
if (createNewPackage && originalPkgSetting != null) {
// This is the initial transition from the original package, so,
@@ -14643,8 +14344,8 @@
* Adds a scanned package to the system. When this method is finished, the package will
* be available for query, resolution, etc...
*/
- private void commitPackageSettings(AndroidPackage pkg,
- @Nullable AndroidPackage oldPkg, PackageSetting pkgSetting,
+ private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
+ @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
final @ScanFlags int scanFlags, boolean chatty, ReconciledPackage reconciledPkg) {
final String pkgName = pkg.getPackageName();
if (mCustomResolverComponentName != null &&
@@ -14767,6 +14468,12 @@
mAppsFilter.addPackage(pkgSetting, isReplace);
mPackageProperty.addAllProperties(pkg);
+ if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
+ mDomainVerificationManager.addPackage(pkgSetting);
+ } else {
+ mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+ }
+
int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
StringBuilder r = null;
int i;
@@ -16439,77 +16146,31 @@
return DEFAULT_INTEGRITY_VERIFY_ENABLE;
}
+ @Deprecated
@Override
- public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
- throws RemoteException {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
- "Only intentfilter verification agents can verify applications");
-
- final Message msg = mHandler.obtainMessage(INTENT_FILTER_VERIFIED);
- final IntentFilterVerificationResponse response = new IntentFilterVerificationResponse(
- Binder.getCallingUid(), verificationCode, failedDomains);
- msg.arg1 = id;
- msg.obj = response;
- mHandler.sendMessage(msg);
+ public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) {
+ DomainVerificationProxyV1.queueLegacyVerifyResult(mContext, mDomainVerificationConnection,
+ id, verificationCode, failedDomains, Binder.getCallingUid());
}
+ @Deprecated
@Override
public int getIntentVerificationStatus(String packageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- if (UserHandle.getUserId(callingUid) != userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "getIntentVerificationStatus" + userId);
- }
- if (getInstantAppPackageName(callingUid) != null) {
- return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null
- || shouldFilterApplicationLocked(
- ps, callingUid, UserHandle.getUserId(callingUid))) {
- return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
- return mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
- }
+ return mDomainVerificationManager.getLegacyState(packageName, userId);
}
+ @Deprecated
@Override
public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-
- boolean result = false;
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (shouldFilterApplicationLocked(
- ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
- return false;
- }
- result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId);
- }
- if (result) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- return result;
+ mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
+ return true;
}
+ @Deprecated
@Override
public @NonNull ParceledListSlice<IntentFilterVerificationInfo> getIntentFilterVerifications(
String packageName) {
- final int callingUid = Binder.getCallingUid();
- if (getInstantAppPackageName(callingUid) != null) {
- return ParceledListSlice.emptyList();
- }
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (shouldFilterApplicationLocked(ps, callingUid, UserHandle.getUserId(callingUid))) {
- return ParceledListSlice.emptyList();
- }
- return new ParceledListSlice<>(mSettings.getIntentFilterVerificationsLPr(packageName));
- }
+ return ParceledListSlice.emptyList();
}
@Override
@@ -20192,13 +19853,6 @@
"Failed to set up verity: " + e);
}
- if (!instantApp) {
- startIntentFilterVerifications(args.user.getIdentifier(), replace, parsedPackage);
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Not verifying instant app install for app links: " + pkgName);
- }
- }
final PackageFreezer freezer =
freezePackageForInstall(pkgName, installFlags, "installPackageLI");
boolean shouldCloseFreezerBeforeReturn = true;
@@ -20532,190 +20186,6 @@
}
}
- private void startIntentFilterVerifications(int userId, boolean replacing, AndroidPackage pkg) {
- if (mIntentFilterVerifierComponent == null) {
- Slog.w(TAG, "No IntentFilter verification will not be done as "
- + "there is no IntentFilterVerifier available!");
- return;
- }
-
- final int verifierUid = getPackageUid(
- mIntentFilterVerifierComponent.getPackageName(),
- MATCH_DEBUG_TRIAGED_MISSING,
- (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
-
- Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS);
- msg.obj = new IFVerificationParams(
- pkg.getPackageName(),
- pkg.isHasDomainUrls(),
- pkg.getActivities(),
- replacing,
- userId,
- verifierUid
- );
- mHandler.sendMessage(msg);
- }
-
- private void verifyIntentFiltersIfNeeded(int userId, int verifierUid, boolean replacing,
- String packageName,
- boolean hasDomainUrls,
- List<ParsedActivity> activities) {
- int size = activities.size();
- if (size == 0) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "No activity, so no need to verify any IntentFilter!");
- return;
- }
-
- if (!hasDomainUrls) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "No domain URLs, so no need to verify any IntentFilter!");
- return;
- }
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Checking for userId:" + userId
- + " if any IntentFilter from the " + size
- + " Activities needs verification ...");
-
- int count = 0;
- boolean handlesWebUris = false;
- ArraySet<String> domains = new ArraySet<>();
- final boolean previouslyVerified;
- boolean hostSetExpanded = false;
- boolean needToRunVerify = false;
- synchronized (mLock) {
- // If this is a new install and we see that we've already run verification for this
- // package, we have nothing to do: it means the state was restored from backup.
- IntentFilterVerificationInfo ivi =
- mSettings.getIntentFilterVerificationLPr(packageName);
- previouslyVerified = (ivi != null);
- if (!replacing && previouslyVerified) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Package " + packageName + " already verified: status="
- + ivi.getStatusString());
- }
- return;
- }
-
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, " Previous verified hosts: "
- + (ivi == null ? "[none]" : ivi.getDomainsString()));
- }
-
- // If any filters need to be verified, then all need to be. In addition, we need to
- // know whether an updating app has any web navigation intent filters, to re-
- // examine handling policy even if not re-verifying.
- final boolean needsVerification = needsNetworkVerificationLPr(packageName);
- for (ParsedActivity a : activities) {
- for (ParsedIntentInfo filter : a.getIntents()) {
- if (filter.handlesWebUris(true)) {
- handlesWebUris = true;
- }
- if (needsVerification && filter.needsVerification()) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "autoVerify requested, processing all filters");
- }
- needToRunVerify = true;
- // It's safe to break out here because filter.needsVerification()
- // can only be true if filter.handlesWebUris(true) returned true, so
- // we've already noted that.
- break;
- }
- }
- }
-
- // Compare the new set of recognized hosts if the app is either requesting
- // autoVerify or has previously used autoVerify but no longer does.
- if (needToRunVerify || previouslyVerified) {
- final int verificationId = mIntentFilterVerificationToken++;
- for (ParsedActivity a : activities) {
- for (ParsedIntentInfo filter : a.getIntents()) {
- // Run verification against hosts mentioned in any web-nav intent filter,
- // even if the filter matches non-web schemes as well
- if (filter.handlesWebUris(false /*onlyWebSchemes*/)) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "Verification needed for IntentFilter:" + filter.toString());
- mIntentFilterVerifier.addOneIntentFilterVerification(
- verifierUid, userId, verificationId, filter, packageName);
- domains.addAll(filter.getHostsList());
- count++;
- }
- }
- }
- }
-
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, " Update published hosts: " + domains.toString());
- }
-
- // If we've previously verified this same host set (or a subset), we can trust that
- // a current ALWAYS policy is still applicable. If this is the case, we're done.
- // (If we aren't in ALWAYS, we want to reverify to allow for apps that had failing
- // hosts in their intent filters, then pushed a new apk that removed them and now
- // passes.)
- //
- // Cases:
- // + still autoVerify (needToRunVerify):
- // - preserve current state if all of: unexpanded, in always
- // - otherwise rerun as usual (fall through)
- // + no longer autoVerify (alreadyVerified && !needToRunVerify)
- // - wipe verification history always
- // - preserve current state if all of: unexpanded, in always
- hostSetExpanded = !previouslyVerified
- || (ivi != null && !ivi.getDomains().containsAll(domains));
- final int currentPolicy =
- mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
- final boolean keepCurState = !hostSetExpanded
- && currentPolicy == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-
- if (needToRunVerify && keepCurState) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Host set not expanding + ALWAYS -> no need to reverify");
- }
- ivi.setDomains(domains);
- scheduleWriteSettingsLocked();
- return;
- } else if (previouslyVerified && !needToRunVerify) {
- // Prior autoVerify state but not requesting it now. Clear autoVerify history,
- // and preserve the always policy iff the host set is not expanding.
- clearIntentFilterVerificationsLPw(packageName, userId, !keepCurState);
- return;
- }
- }
-
- if (needToRunVerify && count > 0) {
- // app requested autoVerify and has at least one matching intent filter
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Starting " + count
- + " IntentFilter verification" + (count > 1 ? "s" : "")
- + " for userId:" + userId);
- mIntentFilterVerifier.startVerifications(userId);
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "No web filters or no new host policy for " + packageName);
- }
- }
- }
-
- @GuardedBy("mLock")
- private boolean needsNetworkVerificationLPr(String packageName) {
- IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr(
- packageName);
- if (ivi == null) {
- return true;
- }
- int status = ivi.getStatus();
- switch (status) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
- return true;
-
- default:
- // Nothing to do
- return false;
- }
- }
-
private static boolean isExternal(PackageSetting ps) {
return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
@@ -21385,7 +20855,7 @@
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mLock) {
- clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL, true);
+ mDomainVerificationManager.clearPackage(deletedPs.name);
clearDefaultBrowserIfNeeded(packageName);
mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
mAppsFilter.removePackage(getPackageSetting(packageName));
@@ -21912,8 +21382,6 @@
null /*lastDisableAppCaller*/,
null /*enabledComponents*/,
null /*disabledComponents*/,
- ps.readUserState(nextUserId).domainVerificationStatus,
- 0 /*linkGeneration*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/);
@@ -22463,40 +21931,6 @@
mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId);
}
- /** This method takes a specific user id as well as UserHandle.USER_ALL. */
- @GuardedBy("mLock")
- private void clearIntentFilterVerificationsLPw(int userId) {
- final int packageCount = mPackages.size();
- for (int i = 0; i < packageCount; i++) {
- AndroidPackage pkg = mPackages.valueAt(i);
- clearIntentFilterVerificationsLPw(pkg.getPackageName(), userId, true);
- }
- }
-
- /** This method takes a specific user id as well as UserHandle.USER_ALL. */
- @GuardedBy("mLock")
- void clearIntentFilterVerificationsLPw(String packageName, int userId,
- boolean alsoResetStatus) {
- if (SystemConfig.getInstance().getLinkedApps().contains(packageName)) {
- // Nope, need to preserve the system configuration approval for this app
- return;
- }
-
- if (userId == UserHandle.USER_ALL) {
- if (mSettings.removeIntentFilterVerificationLPw(packageName,
- mUserManager.getUserIds())) {
- for (int oneUserId : mUserManager.getUserIds()) {
- scheduleWritePackageRestrictionsLocked(oneUserId);
- }
- }
- } else {
- if (mSettings.removeIntentFilterVerificationLPw(packageName, userId,
- alsoResetStatus)) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
- }
-
/** Clears state for all users, and touches intent filter verification policy */
void clearDefaultBrowserIfNeeded(String packageName) {
for (int oneUserId : mUserManager.getUserIds()) {
@@ -22552,8 +21986,7 @@
}
synchronized (mLock) {
mSettings.applyDefaultPreferredAppsLPw(userId);
- clearIntentFilterVerificationsLPw(userId);
- primeDomainVerificationsLPw(userId);
+ mDomainVerificationManager.clearUser(userId);
final int numPackages = mPackages.size();
for (int i = 0; i < numPackages; i++) {
final AndroidPackage pkg = mPackages.valueAt(i);
@@ -22815,28 +22248,8 @@
throw new SecurityException("Only the system may call getIntentFilterVerificationBackup()");
}
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- try {
- final TypedXmlSerializer serializer = Xml.newFastSerializer();
- serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_INTENT_FILTER_VERIFICATION);
-
- synchronized (mLock) {
- mSettings.writeAllDomainVerificationsLPr(serializer, userId);
- }
-
- serializer.endTag(null, TAG_INTENT_FILTER_VERIFICATION);
- serializer.endDocument();
- serializer.flush();
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Unable to write default apps for backup", e);
- }
- return null;
- }
-
- return dataStream.toByteArray();
+ // TODO(b/170746586)
+ return null;
}
@Override
@@ -22844,22 +22257,7 @@
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only the system may call restorePreferredActivities()");
}
-
- try {
- final TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
- restoreFromXml(parser, userId, TAG_INTENT_FILTER_VERIFICATION,
- (parser1, userId1) -> {
- synchronized (mLock) {
- mSettings.readAllDomainVerificationsLPr(parser1, userId1);
- writeSettingsLPrTEMP();
- }
- });
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
- }
- }
+ // TODO(b/170746586)
}
@Override
@@ -24112,8 +23510,8 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new PackageManagerShellCommand(this, mContext)).exec(
- this, in, out, err, args, callback, resultReceiver);
+ (new PackageManagerShellCommand(this, mContext,mDomainVerificationManager.getShell()))
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
@SuppressWarnings("resource")
@@ -24295,9 +23693,8 @@
dumpState.setDump(DumpState.DUMP_MESSAGES);
} else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERIFIERS);
- } else if ("i".equals(cmd) || "ifv".equals(cmd)
- || "intent-filter-verifiers".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_INTENT_FILTER_VERIFIERS);
+ } else if ("dv".equals(cmd) || "domain-verifier".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_DOMAIN_VERIFIER);
} else if ("version".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERSION);
} else if ("k".equals(cmd) || "keysets".equals(cmd)) {
@@ -24391,14 +23788,16 @@
}
}
- if (dumpState.isDumping(DumpState.DUMP_INTENT_FILTER_VERIFIERS) &&
+ if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER) &&
packageName == null) {
- if (mIntentFilterVerifierComponent != null) {
- String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+ DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
+ ComponentName verifierComponent = proxy.getComponentName();
+ if (verifierComponent != null) {
+ String verifierPackageName = verifierComponent.getPackageName();
if (!checkin) {
if (dumpState.onTitlePrinted())
pw.println();
- pw.println("Intent Filter Verifier:");
+ pw.println("Domain Verifier:");
pw.print(" Using: ");
pw.print(verifierPackageName);
pw.print(" (uid=");
@@ -24406,14 +23805,14 @@
UserHandle.USER_SYSTEM));
pw.println(")");
} else if (verifierPackageName != null) {
- pw.print("ifv,"); pw.print(verifierPackageName);
+ pw.print("dv,"); pw.print(verifierPackageName);
pw.print(",");
pw.println(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
UserHandle.USER_SYSTEM));
}
} else {
pw.println();
- pw.println("No Intent Filter Verifier available!");
+ pw.println("No Domain Verifier available!");
}
}
@@ -24530,63 +23929,20 @@
}
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)
- && packageName == null) {
- pw.println();
- int count = mSettings.getPackagesLocked().size();
- if (count == 0) {
- pw.println("No applications!");
- pw.println();
- } else {
- final String prefix = " ";
- Collection<PackageSetting> allPackageSettings =
- mSettings.getPackagesLocked().values();
- if (allPackageSettings.size() == 0) {
- pw.println("No domain preferred apps!");
- pw.println();
- } else {
- pw.println("App verification status:");
- pw.println();
- count = 0;
- for (PackageSetting ps : allPackageSettings) {
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi == null || ivi.getPackageName() == null) continue;
- pw.println(prefix + "Package: " + ivi.getPackageName());
- pw.println(prefix + "Domains: " + ivi.getDomainsString());
- pw.println(prefix + "Status: " + ivi.getStatusString());
- pw.println();
- count++;
- }
- if (count == 0) {
- pw.println(prefix + "No app verification established.");
- pw.println();
- }
- for (int userId : mUserManager.getUserIds()) {
- pw.println("App linkages for user " + userId + ":");
- pw.println();
- count = 0;
- for (PackageSetting ps : allPackageSettings) {
- final long status = ps.getDomainVerificationStatusForUser(userId);
- if (status >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
- && !DEBUG_DOMAIN_VERIFICATION) {
- continue;
- }
- pw.println(prefix + "Package: " + ps.name);
- pw.println(prefix + "Domains: " + dumpDomainString(ps.name));
- String statusStr = IntentFilterVerificationInfo.
- getStatusStringFromValue(status);
- pw.println(prefix + "Status: " + statusStr);
- pw.println();
- count++;
- }
- if (count == 0) {
- pw.println(prefix + "No configured app linkages.");
- pw.println();
- }
- }
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+ android.util.IndentingPrintWriter writer =
+ new android.util.IndentingPrintWriter(pw);
+ if (dumpState.onTitlePrinted()) pw.println();
+
+ writer.println("Domain verification status:");
+ writer.increaseIndent();
+ try {
+ mDomainVerificationManager.printState(writer, packageName, UserHandle.USER_ALL);
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Failure printing domain verification information");
+ Slog.e(TAG, "Failure printing domain verification information", e);
}
+ writer.decreaseIndent();
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
@@ -24775,8 +24131,10 @@
UserHandle.USER_SYSTEM));
proto.end(requiredVerifierPackageToken);
- if (mIntentFilterVerifierComponent != null) {
- String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+ DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
+ ComponentName verifierComponent = proxy.getComponentName();
+ if (verifierComponent != null) {
+ String verifierPackageName = verifierComponent.getPackageName();
final long verifierPackageToken =
proto.start(PackageServiceDumpProto.VERIFIER_PACKAGE);
proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName);
@@ -24901,35 +24259,6 @@
}
}
- private String dumpDomainString(String packageName) {
- List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
- .getList();
- List<IntentFilter> filters = getAllIntentFilters(packageName).getList();
-
- ArraySet<String> result = new ArraySet<>();
- if (iviList.size() > 0) {
- for (IntentFilterVerificationInfo ivi : iviList) {
- result.addAll(ivi.getDomains());
- }
- }
- if (filters != null && filters.size() > 0) {
- for (IntentFilter filter : filters) {
- if (filter.hasCategory(Intent.CATEGORY_BROWSABLE)
- && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
- filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) {
- result.addAll(filter.getHostsList());
- }
- }
- }
-
- StringBuilder sb = new StringBuilder(result.size() * 16);
- for (String domain : result) {
- if (sb.length() > 0) sb.append(" ");
- sb.append(domain);
- }
- return sb.toString();
- }
-
// ------- apps on sdcard specific code -------
static final boolean DEBUG_SD_INSTALL = false;
@@ -26125,7 +25454,6 @@
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
scheduleWritePackageListLocked(userId);
- primeDomainVerificationsLPw(userId);
mAppsFilter.onUsersChanged();
}
}
@@ -27187,6 +26515,7 @@
final boolean instantApp =
isInstantAppInternal(visiblePackage.getPackageName(), userId, visibleUid);
+ final boolean accessGranted;
if (instantApp) {
if (!direct) {
// if the interaction that lead to this granting access to an instant app
@@ -27194,10 +26523,13 @@
// grant.
return;
}
- mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
+ accessGranted = mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/);
} else {
- mAppsFilter.grantImplicitAccess(recipientUid, visibleUid);
+ accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid);
+ }
+ if (accessGranted) {
+ ApplicationPackageManager.invalidateGetPackagesForUidCache();
}
}
}
@@ -28421,6 +27753,11 @@
duration);
return bOptions;
}
+
+ @NonNull
+ public DomainVerificationService.Connection getDomainVerificationConnection() {
+ return mDomainVerificationConnection;
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 3207d56a..b5765b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -17,13 +17,9 @@
package com.android.server.pm;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import android.accounts.IAccountManager;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -108,6 +104,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
+import com.android.server.pm.verify.domain.DomainVerificationShell;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import dalvik.system.DexFile;
@@ -122,6 +119,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
@@ -148,6 +146,7 @@
final LegacyPermissionManagerInternal mLegacyPermissionManager;
final PermissionManager mPermissionManager;
final Context mContext;
+ final DomainVerificationShell mDomainVerificationShell;
final private WeakHashMap<String, Resources> mResourceCache =
new WeakHashMap<String, Resources>();
int mTargetUser;
@@ -155,11 +154,15 @@
boolean mComponents;
int mQueryFlags;
- PackageManagerShellCommand(PackageManagerService service, Context context) {
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ PackageManagerShellCommand(@NonNull PackageManagerService service,
+ @NonNull Context context, @NonNull DomainVerificationShell domainVerificationShell) {
mInterface = service;
mLegacyPermissionManager = LocalServices.getService(LegacyPermissionManagerInternal.class);
mPermissionManager = context.getSystemService(PermissionManager.class);
mContext = context;
+ mDomainVerificationShell = domainVerificationShell;
}
@Override
@@ -264,10 +267,6 @@
return runGetPrivappDenyPermissions();
case "get-oem-permissions":
return runGetOemPermissions();
- case "set-app-link":
- return runSetAppLink();
- case "get-app-link":
- return runGetAppLink();
case "trim-caches":
return runTrimCaches();
case "create-user":
@@ -306,6 +305,12 @@
case "bypass-staged-installer-check":
return runBypassStagedInstallerCheck();
default: {
+ Boolean domainVerificationResult =
+ mDomainVerificationShell.runCommand(this, cmd);
+ if (domainVerificationResult != null) {
+ return domainVerificationResult ? 0 : 1;
+ }
+
String nextArg = getNextArg();
if (nextArg == null) {
if (cmd.equalsIgnoreCase("-l")) {
@@ -2424,134 +2429,6 @@
return 0;
}
- private String linkStateToString(int state) {
- switch (state) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
- }
- return "Unknown link state: " + state;
- }
-
- // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
- private int runSetAppLink() throws RemoteException {
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- if (opt.equals("--user")) {
- userId = UserHandle.parseUserArg(getNextArgRequired());
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return 1;
- }
- }
-
- // Package name to act on; required
- final String pkg = getNextArg();
- if (pkg == null) {
- getErrPrintWriter().println("Error: no package specified.");
- return 1;
- }
-
- // State to apply; {always|ask|never|undefined}, required
- final String modeString = getNextArg();
- if (modeString == null) {
- getErrPrintWriter().println("Error: no app link state specified.");
- return 1;
- }
-
- final int newMode;
- switch (modeString.toLowerCase()) {
- case "undefined":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- break;
-
- case "always":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- break;
-
- case "ask":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
- break;
-
- case "always-ask":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
- break;
-
- case "never":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
- break;
-
- default:
- getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'");
- return 1;
- }
-
- final int translatedUserId =
- translateUserId(userId, UserHandle.USER_NULL, "runSetAppLink");
- final PackageInfo info = mInterface.getPackageInfo(pkg, 0, translatedUserId);
- if (info == null) {
- getErrPrintWriter().println("Error: package " + pkg + " not found.");
- return 1;
- }
-
- if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
- getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
- return 1;
- }
-
- if (!mInterface.updateIntentVerificationStatus(pkg, newMode, translatedUserId)) {
- getErrPrintWriter().println("Error: unable to update app link status for " + pkg);
- return 1;
- }
-
- return 0;
- }
-
- // pm get-app-link [--user USER_ID] PACKAGE
- private int runGetAppLink() throws RemoteException {
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- if (opt.equals("--user")) {
- userId = UserHandle.parseUserArg(getNextArgRequired());
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return 1;
- }
- }
-
- // Package name to act on; required
- final String pkg = getNextArg();
- if (pkg == null) {
- getErrPrintWriter().println("Error: no package specified.");
- return 1;
- }
-
- final int translatedUserId =
- translateUserId(userId, UserHandle.USER_NULL, "runGetAppLink");
- final PackageInfo info = mInterface.getPackageInfo(pkg, 0, translatedUserId);
- if (info == null) {
- getErrPrintWriter().println("Error: package " + pkg + " not found.");
- return 1;
- }
-
- if ((info.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
- getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
- return 1;
- }
-
- getOutPrintWriter().println(linkStateToString(
- mInterface.getIntentVerificationStatus(pkg, translatedUserId)));
-
- return 0;
- }
-
private int runTrimCaches() throws RemoteException {
String size = getNextArg();
if (size == null) {
@@ -3146,7 +3023,7 @@
// 1. Single file from stdin.
if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
- final String name = "base." + (isApex ? "apex" : "apk");
+ final String name = "base" + RANDOM.nextInt() + "." + (isApex ? "apex" : "apk");
final Metadata metadata = Metadata.forStdIn(name);
session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
metadata.toByteArray(), null);
@@ -3912,6 +3789,8 @@
pw.println(" --enable: turn on debug logging (default)");
pw.println(" --disable: turn off debug logging");
pw.println("");
+ mDomainVerificationShell.printHelp(pw);
+ pw.println("");
Intent.printIntentArgsHelp(pw , "");
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index ade087b..69e84b5 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -39,6 +39,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
/**
* Settings data for a particular package we know about.
@@ -99,18 +100,23 @@
@NonNull
private PackageStateUnserialized pkgState = new PackageStateUnserialized();
+ @NonNull
+ private UUID mDomainSetId;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public PackageSetting(String name, String realName, @NonNull File codePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
long pVersionCode, int pkgFlags, int privateFlags,
int sharedUserId, String[] usesStaticLibraries,
- long[] usesStaticLibrariesVersions, Map<String, ArraySet<String>> mimeGroups) {
+ long[] usesStaticLibrariesVersions, Map<String, ArraySet<String>> mimeGroups,
+ @NonNull UUID domainSetId) {
super(name, realName, codePath, legacyNativeLibraryPathString,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
pVersionCode, pkgFlags, privateFlags,
usesStaticLibraries, usesStaticLibrariesVersions);
this.sharedUserId = sharedUserId;
+ mDomainSetId = domainSetId;
copyMimeGroups(mimeGroups);
}
@@ -168,6 +174,7 @@
sharedUser = orig.sharedUser;
sharedUserId = orig.sharedUserId;
copyMimeGroups(orig.mimeGroups);
+ mDomainSetId = orig.getDomainSetId();
}
private void copyMimeGroups(@Nullable Map<String, ArraySet<String>> newMimeGroups) {
@@ -374,6 +381,7 @@
pkg = other.pkg;
sharedUserId = other.sharedUserId;
sharedUser = other.sharedUser;
+ mDomainSetId = other.mDomainSetId;
Set<String> mimeGroupNames = other.mimeGroups != null ? other.mimeGroups.keySet() : null;
updateMimeGroups(mimeGroupNames);
@@ -385,4 +393,14 @@
public PackageStateUnserialized getPkgState() {
return pkgState;
}
+
+ @NonNull
+ public UUID getDomainSetId() {
+ return mDomainSetId;
+ }
+
+ public PackageSetting setDomainSetId(@NonNull UUID domainSetId) {
+ mDomainSetId = domainSetId;
+ return this;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 67bd82b4..8aa553d 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -42,6 +42,8 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.File;
@@ -131,8 +133,6 @@
/** Whether or not an update is available. Ostensibly only for instant apps. */
boolean updateAvailable;
- IntentFilterVerificationInfo verificationInfo;
-
boolean forceQueryableOverride;
@NonNull
@@ -258,7 +258,6 @@
for (int i = 0; i < orig.mUserState.size(); i++) {
mUserState.put(orig.mUserState.keyAt(i), orig.mUserState.valueAt(i));
}
- verificationInfo = orig.verificationInfo;
versionCode = orig.versionCode;
volumeUuid = orig.volumeUuid;
categoryHint = orig.categoryHint;
@@ -350,9 +349,12 @@
return readUserState(userId).getSharedLibraryOverlayPaths();
}
- /** Only use for testing. Do NOT use in production code. */
+ /**
+ * Only use for testing. Do NOT use in production code.
+ */
@VisibleForTesting
- SparseArray<PackageUserState> getUserState() {
+ @Deprecated
+ public SparseArray<PackageUserState> getUserState() {
return mUserState;
}
@@ -496,8 +498,7 @@
ArrayMap<String, PackageUserState.SuspendParams> suspendParams, boolean instantApp,
boolean virtualPreload, String lastDisableAppCaller,
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
- int domainVerifState, int linkGeneration, int installReason, int uninstallReason,
- String harmfulAppWarning) {
+ int installReason, int uninstallReason, String harmfulAppWarning) {
PackageUserState state = modifyUserState(userId);
state.ceDataInode = ceDataInode;
state.enabled = enabled;
@@ -511,8 +512,6 @@
state.lastDisableAppCaller = lastDisableAppCaller;
state.enabledComponents = enabledComponents;
state.disabledComponents = disabledComponents;
- state.domainVerificationStatus = domainVerifState;
- state.appLinkGeneration = linkGeneration;
state.installReason = installReason;
state.uninstallReason = uninstallReason;
state.instantApp = instantApp;
@@ -528,7 +527,6 @@
otherState.instantApp,
otherState.virtualPreload, otherState.lastDisableAppCaller,
otherState.enabledComponents, otherState.disabledComponents,
- otherState.domainVerificationStatus, otherState.appLinkGeneration,
otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning);
}
@@ -644,40 +642,6 @@
return excludedUserIds;
}
- IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
- return verificationInfo;
- }
-
- void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) {
- verificationInfo = info;
- onChanged();
- }
-
- // Returns a packed value as a long:
- //
- // high 'int'-sized word: link status: undefined/ask/never/always.
- // low 'int'-sized word: relative priority among 'always' results.
- long getDomainVerificationStatusForUser(int userId) {
- PackageUserState state = readUserState(userId);
- long result = (long) state.appLinkGeneration;
- result |= ((long) state.domainVerificationStatus) << 32;
- return result;
- }
-
- void setDomainVerificationStatusForUser(final int status, int generation, int userId) {
- PackageUserState state = modifyUserState(userId);
- state.domainVerificationStatus = status;
- if (status == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- state.appLinkGeneration = generation;
- onChanged();
- }
- }
-
- void clearDomainVerificationStatusForUser(int userId) {
- modifyUserState(userId).domainVerificationStatus =
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
-
protected void writeUsersInfoToProto(ProtoOutputStream proto, long fieldId) {
int count = mUserState.size();
for (int i = 0; i < count; i++) {
@@ -845,7 +809,6 @@
this.volumeUuid = other.volumeUuid;
this.categoryHint = other.categoryHint;
this.updateAvailable = other.updateAvailable;
- this.verificationInfo = other.verificationInfo;
this.forceQueryableOverride = other.forceQueryableOverride;
this.incrementalStates = other.incrementalStates;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2929568..fb033e6 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -21,15 +21,12 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.UNINSTALL_REASON_USER_TYPE;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
-import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.NonNull;
@@ -108,6 +105,9 @@
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;
@@ -131,6 +131,7 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedWriter;
import java.io.File;
@@ -147,7 +148,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -155,6 +155,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
/**
* Holds information about dynamic settings.
@@ -283,9 +284,9 @@
"persistent-preferred-activities";
static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
"crossProfile-intent-filters";
- private static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
+ public static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
private static final String TAG_DEFAULT_APPS = "default-apps";
- private static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
+ public static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
"all-intent-filter-verifications";
private static final String TAG_DEFAULT_BROWSER = "default-browser";
private static final String TAG_DEFAULT_DIALER = "default-dialer";
@@ -390,12 +391,6 @@
private final WatchedSparseArray<ArraySet<String>> mBlockUninstallPackages =
new WatchedSparseArray<>();
- // Set of restored intent-filter verification states
- @Watched
- private final WatchedArrayMap<String, IntentFilterVerificationInfo>
- mRestoredIntentFilterVerifications =
- new WatchedArrayMap<String, IntentFilterVerificationInfo>();
-
private static final class KernelPackageState {
int appId;
int[] excludedUserIds;
@@ -487,7 +482,10 @@
@Watched
final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
+ // TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
+ // verify.
// App-link priority tracking, per-user
+ @NonNull
@Watched
final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
@@ -512,6 +510,8 @@
private final LegacyPermissionDataProvider mPermissionDataProvider;
+ private final DomainVerificationManagerInternal mDomainVerificationManager;
+
/**
* The observer that watches for changes from array members
*/
@@ -538,13 +538,12 @@
mStoppedPackagesFilename = null;
mBackupStoppedPackagesFilename = null;
mKernelMappingFilename = null;
-
+ mDomainVerificationManager = null;
mPackages.registerObserver(mObserver);
mInstallerPackages.registerObserver(mObserver);
mKernelMapping.registerObserver(mObserver);
mDisabledSysPackages.registerObserver(mObserver);
mBlockUninstallPackages.registerObserver(mObserver);
- mRestoredIntentFilterVerifications.registerObserver(mObserver);
mVersion.registerObserver(mObserver);
mPreferredActivities.registerObserver(mObserver);
mPersistentPreferredActivities.registerObserver(mObserver);
@@ -554,13 +553,14 @@
mOtherAppIds.registerObserver(mObserver);
mRenamedPackages.registerObserver(mObserver);
mDefaultBrowserApp.registerObserver(mObserver);
- mNextAppLinkGeneration.registerObserver(mObserver);
Watchable.verifyWatchedAttributes(this, mObserver);
}
Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence,
- LegacyPermissionDataProvider permissionDataProvider, Object lock) {
+ LegacyPermissionDataProvider permissionDataProvider,
+ @NonNull DomainVerificationManagerInternal domainVerificationManager,
+ @NonNull Object lock) {
mLock = lock;
mAppIds = new WatchedArrayList<>();
mOtherAppIds = new WatchedSparseArray<>();
@@ -587,12 +587,13 @@
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
+ mDomainVerificationManager = domainVerificationManager;
+
mPackages.registerObserver(mObserver);
mInstallerPackages.registerObserver(mObserver);
mKernelMapping.registerObserver(mObserver);
mDisabledSysPackages.registerObserver(mObserver);
mBlockUninstallPackages.registerObserver(mObserver);
- mRestoredIntentFilterVerifications.registerObserver(mObserver);
mVersion.registerObserver(mObserver);
mPreferredActivities.registerObserver(mObserver);
mPersistentPreferredActivities.registerObserver(mObserver);
@@ -602,7 +603,6 @@
mOtherAppIds.registerObserver(mObserver);
mRenamedPackages.registerObserver(mObserver);
mDefaultBrowserApp.registerObserver(mObserver);
- mNextAppLinkGeneration.registerObserver(mObserver);
Watchable.verifyWatchedAttributes(this, mObserver);
}
@@ -629,11 +629,12 @@
mBackupStoppedPackagesFilename = null;
mKernelMappingFilename = null;
+ mDomainVerificationManager = r.mDomainVerificationManager;
+
mInstallerPackages.addAll(r.mInstallerPackages);
mKernelMapping.putAll(r.mKernelMapping);
mDisabledSysPackages.putAll(r.mDisabledSysPackages);
mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages);
- mRestoredIntentFilterVerifications.putAll(r.mRestoredIntentFilterVerifications);
mVersion.putAll(r.mVersion);
mVerifierDeviceIdentity = r.mVerifierDeviceIdentity;
WatchedSparseArray.snapshot(
@@ -649,7 +650,6 @@
mKeySetRefs.putAll(r.mKeySetRefs);
mRenamedPackages.snapshot(r.mRenamedPackages);
mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
- mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
// mReadMessages
mPendingPackages.addAll(r.mPendingPackages);
mSystemDir = null;
@@ -766,7 +766,8 @@
p.legacyNativeLibraryPathString, p.primaryCpuAbiString,
p.secondaryCpuAbiString, p.cpuAbiOverrideString,
p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags,
- p.usesStaticLibraries, p.usesStaticLibrariesVersions, p.mimeGroups);
+ p.usesStaticLibraries, p.usesStaticLibrariesVersions, p.mimeGroups,
+ mDomainVerificationManager.generateNewId());
if (ret != null) {
ret.getPkgState().setUpdatedSystemApp(false);
}
@@ -786,7 +787,8 @@
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc, int
pkgFlags, int pkgPrivateFlags, String[] usesStaticLibraries,
- long[] usesStaticLibraryNames, Map<String, ArraySet<String>> mimeGroups) {
+ long[] usesStaticLibraryNames, Map<String, ArraySet<String>> mimeGroups,
+ @NonNull UUID domainSetId) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.appId == uid) {
@@ -799,7 +801,7 @@
p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
pkgPrivateFlags, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames,
- mimeGroups);
+ mimeGroups, domainSetId);
p.appId = uid;
if (registerExistingAppIdLPw(uid, p, name)) {
mPackages.put(name, p);
@@ -863,7 +865,7 @@
UserHandle installUser, boolean allowInstall, boolean instantApp,
boolean virtualPreload, UserManagerService userManager,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames) {
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -883,12 +885,13 @@
pkgSetting.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
// Update new package state.
pkgSetting.setTimeStamp(codePath.lastModified());
+ pkgSetting.setDomainSetId(domainSetId);
} else {
pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
0 /*sharedUserId*/, usesStaticLibraries,
- usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames));
+ usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames), domainSetId);
pkgSetting.setTimeStamp(codePath.lastModified());
pkgSetting.sharedUser = sharedUser;
// If this is not a system app, it starts out stopped.
@@ -925,8 +928,6 @@
null /*lastDisableAppCaller*/,
null /*enabledComponents*/,
null /*disabledComponents*/,
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
- 0 /*linkGeneration*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/);
@@ -983,7 +984,7 @@
@Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames)
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
throws PackageManagerException {
final String pkgName = pkgSetting.name;
if (pkgSetting.sharedUser != sharedUser) {
@@ -1059,6 +1060,7 @@
pkgSetting.usesStaticLibrariesVersions = null;
}
pkgSetting.updateMimeGroups(mimeGroupNames);
+ pkgSetting.setDomainSetId(domainSetId);
}
/**
@@ -1168,15 +1170,6 @@
replaceAppIdLPw(p.appId, sharedUser);
}
}
-
- IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.get(p.name);
- if (ivi != null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Applying restored IVI for " + p.name + " : " + ivi.getStatusString());
- }
- mRestoredIntentFilterVerifications.remove(p.name);
- p.setIntentFilterVerificationInfo(ivi);
- }
}
int removePackageLPw(String name) {
@@ -1307,129 +1300,6 @@
return cpir;
}
- /**
- * The following functions suppose that you have a lock for managing access to the
- * mIntentFiltersVerifications map.
- */
-
- /* package protected */
- IntentFilterVerificationInfo getIntentFilterVerificationLPr(String packageName) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return null;
- }
- return ps.getIntentFilterVerificationInfo();
- }
-
- /* package protected */
- IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName,
- ArraySet<String> domains) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return null;
- }
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi == null) {
- ivi = new IntentFilterVerificationInfo(packageName, domains);
- ps.setIntentFilterVerificationInfo(ivi);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(PackageManagerService.TAG,
- "Creating new IntentFilterVerificationInfo for pkg: " + packageName);
- }
- } else {
- ivi.setDomains(domains);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(PackageManagerService.TAG,
- "Setting domains to existing IntentFilterVerificationInfo for pkg: " +
- packageName + " and with domains: " + ivi.getDomainsString());
- }
- }
- return ivi;
- }
-
- int getIntentFilterVerificationStatusLPr(String packageName, int userId) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
- return (int)(ps.getDomainVerificationStatusForUser(userId) >> 32);
- }
-
- boolean updateIntentFilterVerificationStatusLPw(String packageName, final int status, int userId) {
- // Update the status for the current package
- PackageSetting current = mPackages.get(packageName);
- if (current == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return false;
- }
-
- final int alwaysGeneration;
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- alwaysGeneration = mNextAppLinkGeneration.get(userId) + 1;
- mNextAppLinkGeneration.put(userId, alwaysGeneration);
- } else {
- alwaysGeneration = 0;
- }
-
- current.setDomainVerificationStatusForUser(status, alwaysGeneration, userId);
- return true;
- }
-
- /**
- * Used for Settings App and PackageManagerService dump. Should be read only.
- */
- List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr(
- String packageName) {
- if (packageName == null) {
- return Collections.<IntentFilterVerificationInfo>emptyList();
- }
- ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>();
- for (PackageSetting ps : mPackages.values()) {
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) ||
- !ivi.getPackageName().equalsIgnoreCase(packageName)) {
- continue;
- }
- result.add(ivi);
- }
- return result;
- }
-
- boolean removeIntentFilterVerificationLPw(String packageName, int userId,
- boolean alsoResetStatus) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return false;
- }
- if (alsoResetStatus) {
- ps.clearDomainVerificationStatusForUser(userId);
- }
- ps.setIntentFilterVerificationInfo(null);
- return true;
- }
-
- boolean removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
- boolean result = false;
- for (int userId : userIds) {
- result |= removeIntentFilterVerificationLPw(packageName, userId, true);
- }
- return result;
- }
-
String removeDefaultBrowserPackageNameLPw(int userId) {
return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
}
@@ -1591,40 +1461,7 @@
}
}
- private void readDomainVerificationLPw(TypedXmlPullParser parser,
- PackageSettingBase packageSetting) throws XmlPullParserException, IOException {
- IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
- packageSetting.setIntentFilterVerificationInfo(ivi);
- if (DEBUG_PARSER) {
- Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
- }
- }
-
- private void readRestoredIntentFilterVerifications(TypedXmlPullParser parser)
- throws XmlPullParserException, IOException {
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- final String tagName = parser.getName();
- if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
- IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Restored IVI for " + ivi.getPackageName()
- + " status=" + ivi.getStatusString());
- }
- mRestoredIntentFilterVerifications.put(ivi.getPackageName(), ivi);
- } else {
- Slog.w(TAG, "Unknown element: " + tagName);
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- void readDefaultAppsLPw(TypedXmlPullParser parser, int userId)
+ void readDefaultAppsLPw(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
@@ -1727,8 +1564,6 @@
null /*lastDisableAppCaller*/,
null /*enabledComponents*/,
null /*disabledComponents*/,
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
- 0 /*linkGeneration*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/);
@@ -1753,8 +1588,6 @@
return;
}
- int maxAppLinkGeneration = 0;
-
int outerDepth = parser.getDepth();
PackageSetting ps = null;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1817,11 +1650,6 @@
final int verifState = parser.getAttributeInt(null,
ATTR_DOMAIN_VERIFICATON_STATE,
PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
- final int linkGeneration =
- parser.getAttributeInt(null, ATTR_APP_LINK_GENERATION, 0);
- if (linkGeneration > maxAppLinkGeneration) {
- maxAppLinkGeneration = linkGeneration;
- }
final int installReason = parser.getAttributeInt(null, ATTR_INSTALL_REASON,
PackageManager.INSTALL_REASON_UNKNOWN);
final int uninstallReason = parser.getAttributeInt(null, ATTR_UNINSTALL_REASON,
@@ -1897,9 +1725,10 @@
}
ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
hidden, distractionFlags, suspended, suspendParamsMap,
- instantApp, virtualPreload,
- enabledCaller, enabledComponents, disabledComponents, verifState,
- linkGeneration, installReason, uninstallReason, harmfulAppWarning);
+ instantApp, virtualPreload, enabledCaller, enabledComponents,
+ disabledComponents, installReason, uninstallReason, harmfulAppWarning);
+
+ mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
} else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
@@ -1918,9 +1747,6 @@
}
str.close();
-
- mNextAppLinkGeneration.put(userId, maxAppLinkGeneration + 1);
-
} catch (XmlPullParserException e) {
mReadMessages.append("Error reading: " + e.toString());
PackageManagerService.reportSettingsProblem(Log.ERROR,
@@ -2038,77 +1864,7 @@
serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS);
}
- void writeDomainVerificationsLPr(TypedXmlSerializer serializer,
- IntentFilterVerificationInfo verificationInfo)
- throws IllegalArgumentException, IllegalStateException, IOException {
- if (verificationInfo != null && verificationInfo.getPackageName() != null) {
- serializer.startTag(null, TAG_DOMAIN_VERIFICATION);
- verificationInfo.writeToXml(serializer);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Wrote domain verification for package: "
- + verificationInfo.getPackageName());
- }
- serializer.endTag(null, TAG_DOMAIN_VERIFICATION);
- }
- }
-
- // Specifically for backup/restore
- void writeAllDomainVerificationsLPr(TypedXmlSerializer serializer, int userId)
- throws IllegalArgumentException, IllegalStateException, IOException {
- serializer.startTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
- final int N = mPackages.size();
- for (int i = 0; i < N; i++) {
- PackageSetting ps = mPackages.valueAt(i);
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi != null) {
- writeDomainVerificationsLPr(serializer, ivi);
- }
- }
- serializer.endTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
- }
-
- // Specifically for backup/restore
- void readAllDomainVerificationsLPr(TypedXmlPullParser parser, int userId)
- throws XmlPullParserException, IOException {
- mRestoredIntentFilterVerifications.clear();
-
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
- IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
- final String pkgName = ivi.getPackageName();
- final PackageSetting ps = mPackages.get(pkgName);
- if (ps != null) {
- // known/existing package; update in place
- ps.setIntentFilterVerificationInfo(ivi);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Restored IVI for existing app " + pkgName
- + " status=" + ivi.getStatusString());
- }
- } else {
- mRestoredIntentFilterVerifications.put(pkgName, ivi);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Restored IVI for pending app " + pkgName
- + " status=" + ivi.getStatusString());
- }
- }
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Unknown element under <all-intent-filter-verification>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- void writeDefaultAppsLPr(TypedXmlSerializer serializer, int userId)
+ void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, TAG_DEFAULT_APPS);
String defaultBrowser = mDefaultBrowserApp.get(userId);
@@ -2217,15 +1973,6 @@
ustate.lastDisableAppCaller);
}
}
- if (ustate.domainVerificationStatus !=
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
- serializer.attributeInt(null, ATTR_DOMAIN_VERIFICATON_STATE,
- ustate.domainVerificationStatus);
- }
- if (ustate.appLinkGeneration != 0) {
- serializer.attributeInt(null, ATTR_APP_LINK_GENERATION,
- ustate.appLinkGeneration);
- }
if (ustate.installReason != PackageManager.INSTALL_REASON_UNKNOWN) {
serializer.attributeInt(null, ATTR_INSTALL_REASON, ustate.installReason);
}
@@ -2569,22 +2316,7 @@
}
}
- final int numIVIs = mRestoredIntentFilterVerifications.size();
- if (numIVIs > 0) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Writing restored-ivi entries to packages.xml");
- }
- serializer.startTag(null, "restored-ivi");
- for (int i = 0; i < numIVIs; i++) {
- IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.valueAt(i);
- writeDomainVerificationsLPr(serializer, ivi);
- }
- serializer.endTag(null, "restored-ivi");
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, " no restored IVI entries to write");
- }
- }
+ mDomainVerificationManager.writeSettings(serializer);
mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
@@ -2961,6 +2693,8 @@
serializer.attributeFloat(null, "loadingProgress",
pkg.getIncrementalStates().getProgress());
+ serializer.attribute(null, "domainSetId", pkg.getDomainSetId().toString());
+
writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
@@ -2973,7 +2707,7 @@
writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
writeKeySetAliasesLPr(serializer, pkg.keySetData);
- writeDomainVerificationsLPr(serializer, pkg.verificationInfo);
+ mDomainVerificationManager.writeLegacySettings(serializer, pkg.name);
writeMimeGroupLPr(serializer, pkg.mimeGroups);
serializer.endTag(null, "package");
@@ -3104,8 +2838,6 @@
if (nname != null && oname != null) {
mRenamedPackages.put(nname, oname);
}
- } else if (tagName.equals("restored-ivi")) {
- readRestoredIntentFilterVerifications(parser);
} else if (tagName.equals("last-platform-version")) {
// Upgrade from older XML schema
final VersionInfo internal = findOrCreateVersion(
@@ -3147,6 +2879,11 @@
ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION);
ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
+ } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
+ mDomainVerificationManager.readSettings(parser);
+ } else if (tagName.equals(
+ DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
+ mDomainVerificationManager.readLegacySettings(parser);
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
+ parser.getName());
@@ -3628,9 +3365,15 @@
if (codePathStr.contains("/priv-app/")) {
pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
+
+ // When reading a disabled setting, use a disabled domainSetId, which makes it easier to
+ // debug invalid entries. The actual logic for migrating to a new ID is done in other
+ // methods that use DomainVerificationManagerInternal#generateNewId
+ UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
- versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null);
+ versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null,
+ domainSetId);
long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
if (timeStamp == 0) {
timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3703,6 +3446,7 @@
boolean isStartable = false;
boolean isLoading = false;
float loadingProgress = 0;
+ UUID domainSetId;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -3739,6 +3483,15 @@
categoryHint = parser.getAttributeInt(null, "categoryHint",
ApplicationInfo.CATEGORY_UNDEFINED);
+ String domainSetIdString = parser.getAttributeValue(null, "domainSetId");
+
+ if (TextUtils.isEmpty(domainSetIdString)) {
+ // If empty, assume restoring from previous platform version and generate an ID
+ domainSetId = mDomainVerificationManager.generateNewId();
+ } else {
+ domainSetId = UUID.fromString(domainSetIdString);
+ }
+
systemStr = parser.getAttributeValue(null, "publicFlags");
if (systemStr != null) {
try {
@@ -3810,7 +3563,7 @@
legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
cpuAbiOverrideString, userId, versionCode, pkgFlags, pkgPrivateFlags,
null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
+ userId + " pkg=" + packageSetting);
@@ -3831,7 +3584,7 @@
versionCode, pkgFlags, pkgPrivateFlags, sharedUserId,
null /*usesStaticLibraries*/,
null /*usesStaticLibraryVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/, domainSetId);
packageSetting.setTimeStamp(timeStamp);
packageSetting.firstInstallTime = firstInstallTime;
packageSetting.lastUpdateTime = lastUpdateTime;
@@ -3946,7 +3699,11 @@
packageSetting.installSource =
packageSetting.installSource.setInitiatingPackageSignatures(signatures);
} else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
- readDomainVerificationLPw(parser, packageSetting);
+ IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
+ mDomainVerificationManager.addLegacySetting(packageSetting.name, ivi);
+ if (DEBUG_PARSER) {
+ Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
+ }
} else if (tagName.equals(TAG_MIME_GROUP)) {
packageSetting.mimeGroups = readMimeGroupLPw(parser, packageSetting.mimeGroups);
} else if (tagName.equals(TAG_USES_STATIC_LIB)) {
@@ -4212,6 +3969,7 @@
removeCrossProfileIntentFiltersLPw(userId);
mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
+ mDomainVerificationManager.clearUser(userId);
writePackageListLPr();
@@ -4881,7 +4639,7 @@
}
if (ps.sharedUser == null || permissionNames != null || dumpAll) {
- dumpInstallPermissionsLPr(pw, prefix + " ", permissionNames, permissionsState);
+ dumpInstallPermissionsLPr(pw, prefix + " ", permissionNames, permissionsState, users);
}
if (dumpAllComponents) {
@@ -5131,9 +4889,12 @@
continue;
}
- dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState);
+ List<UserInfo> users = getAllUsers(UserManagerService.getInstance());
- for (int userId : UserManagerService.getInstance().getUserIds()) {
+ dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState, users);
+
+ for (UserInfo user : users) {
+ final int userId = user.id;
final int[] gids = mPermissionDataProvider.getGidsForUid(UserHandle.getUid(
userId, su.userId));
final Collection<PermissionState> permissions =
@@ -5247,31 +5008,55 @@
}
}
- void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames,
- LegacyPermissionState permissionsState) {
- Collection<PermissionState> permissionStates = permissionsState.getPermissionStates(
- UserHandle.USER_SYSTEM);
- boolean hasInstallPermissions = false;
- for (PermissionState permissionState : permissionStates) {
- if (!permissionState.isRuntime()) {
- hasInstallPermissions = true;
- break;
- }
- }
- if (hasInstallPermissions) {
- pw.print(prefix); pw.println("install permissions:");
+ void dumpInstallPermissionsLPr(PrintWriter pw, String prefix,
+ ArraySet<String> filterPermissionNames, LegacyPermissionState permissionsState,
+ List<UserInfo> users) {
+ ArraySet<String> dumpPermissionNames = new ArraySet<>();
+ for (UserInfo user : users) {
+ int userId = user.id;
+ Collection<PermissionState> permissionStates = permissionsState.getPermissionStates(
+ userId);
for (PermissionState permissionState : permissionStates) {
if (permissionState.isRuntime()) {
continue;
}
- if (permissionNames != null
- && !permissionNames.contains(permissionState.getName())) {
+ String permissionName = permissionState.getName();
+ if (filterPermissionNames != null
+ && !filterPermissionNames.contains(permissionName)) {
continue;
}
- pw.print(prefix); pw.print(" "); pw.print(permissionState.getName());
- pw.print(": granted="); pw.print(permissionState.isGranted());
- pw.println(permissionFlagsToString(", flags=",
- permissionState.getFlags()));
+ dumpPermissionNames.add(permissionName);
+ }
+ }
+ boolean printedSomething = false;
+ for (String permissionName : dumpPermissionNames) {
+ PermissionState systemPermissionState = permissionsState.getPermissionState(
+ permissionName, UserHandle.USER_SYSTEM);
+ for (UserInfo user : users) {
+ int userId = user.id;
+ PermissionState permissionState;
+ if (userId == UserHandle.USER_SYSTEM) {
+ permissionState = systemPermissionState;
+ } else {
+ permissionState = permissionsState.getPermissionState(permissionName, userId);
+ if (Objects.equals(permissionState, systemPermissionState)) {
+ continue;
+ }
+ }
+ if (!printedSomething) {
+ pw.print(prefix); pw.println("install permissions:");
+ printedSomething = true;
+ }
+ pw.print(prefix); pw.print(" "); pw.print(permissionName);
+ pw.print(": granted="); pw.print(
+ permissionState != null && permissionState.isGranted());
+ pw.print(permissionFlagsToString(", flags=",
+ permissionState != null ? permissionState.getFlags() : 0));
+ if (userId == UserHandle.USER_SYSTEM) {
+ pw.println();
+ } else {
+ pw.print(", userId="); pw.println(userId);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java
new file mode 100644
index 0000000..9588a27
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SettingsXml.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Stack;
+
+/**
+ * A very specialized serialization/parsing wrapper around {@link TypedXmlSerializer} and {@link
+ * TypedXmlPullParser} intended for use with PackageManager related settings files.
+ * Assumptions/chosen behaviors:
+ * <ul>
+ * <li>No namespace support</li>
+ * <li>Data for a parent object is stored as attributes</li>
+ * <li>All attribute read methods return a default false, -1, or null</li>
+ * <li>Default values will not be written</li>
+ * <li>Children are sub-elements</li>
+ * <li>Collections are repeated sub-elements, no attribute support for collections</li>
+ * </ul>
+ */
+public class SettingsXml {
+
+ private static final String TAG = "SettingsXml";
+
+ private static final boolean DEBUG_THROW_EXCEPTIONS = false;
+
+ private static final String FEATURE_INDENT =
+ "http://xmlpull.org/v1/doc/features.html#indent-output";
+
+ private static final int DEFAULT_NUMBER = -1;
+
+ public static Serializer serializer(TypedXmlSerializer serializer) {
+ return new Serializer(serializer);
+ }
+
+ public static ReadSection parser(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return new ReadSectionImpl(parser);
+ }
+
+ public static class Serializer implements AutoCloseable {
+
+ @NonNull
+ private final TypedXmlSerializer mXmlSerializer;
+
+ private final WriteSectionImpl mWriteSection;
+
+ private Serializer(TypedXmlSerializer serializer) {
+ mXmlSerializer = serializer;
+ mWriteSection = new WriteSectionImpl(mXmlSerializer);
+ }
+
+ public WriteSection startSection(@NonNull String sectionName) throws IOException {
+ return mWriteSection.startSection(sectionName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mWriteSection.closeCompletely();
+ mXmlSerializer.endDocument();
+ }
+ }
+
+ public interface ReadSection extends AutoCloseable {
+
+ @NonNull
+ String getName();
+
+ @NonNull
+ String getDescription();
+
+ boolean has(String attrName);
+
+ @Nullable
+ String getString(String attrName);
+
+ /**
+ * @return value as String or {@param defaultValue} if doesn't exist
+ */
+ @NonNull
+ String getString(String attrName, @NonNull String defaultValue);
+
+ /**
+ * @return value as boolean or false if doesn't exist
+ */
+ boolean getBoolean(String attrName);
+
+ /**
+ * @return value as boolean or {@param defaultValue} if doesn't exist
+ */
+ boolean getBoolean(String attrName, boolean defaultValue);
+
+ /**
+ * @return value as int or {@link #DEFAULT_NUMBER} if doesn't exist
+ */
+ int getInt(String attrName);
+
+ /**
+ * @return value as int or {@param defaultValue} if doesn't exist
+ */
+ int getInt(String attrName, int defaultValue);
+
+ /**
+ * @return value as long or {@link #DEFAULT_NUMBER} if doesn't exist
+ */
+ long getLong(String attrName);
+
+ /**
+ * @return value as long or {@param defaultValue} if doesn't exist
+ */
+ long getLong(String attrName, int defaultValue);
+
+ ChildSection children();
+ }
+
+ /**
+ * <pre><code>
+ * ChildSection child = parentSection.children();
+ * while (child.moveToNext(TAG_CHILD)) {
+ * String readValue = child.getString(...);
+ * ...
+ * }
+ * </code></pre>
+ */
+ public interface ChildSection extends ReadSection {
+ boolean moveToNext();
+
+ boolean moveToNext(@NonNull String expectedChildTagName);
+ }
+
+ public static class ReadSectionImpl implements ChildSection {
+
+ @Nullable
+ private final InputStream mInput;
+
+ @NonNull
+ private final TypedXmlPullParser mParser;
+
+ @NonNull
+ private final Stack<Integer> mDepthStack = new Stack<>();
+
+ public ReadSectionImpl(@NonNull InputStream input)
+ throws IOException, XmlPullParserException {
+ mInput = input;
+ mParser = Xml.newFastPullParser();
+ mParser.setInput(mInput, StandardCharsets.UTF_8.name());
+ moveToFirstTag();
+ }
+
+ public ReadSectionImpl(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ mInput = null;
+ mParser = parser;
+ moveToFirstTag();
+ }
+
+ private void moveToFirstTag() throws IOException, XmlPullParserException {
+ // Move to first tag
+ int type;
+ //noinspection StatementWithEmptyBody
+ while ((type = mParser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return mParser.getName();
+ }
+
+ @NonNull
+ @Override
+ public String getDescription() {
+ return mParser.getPositionDescription();
+ }
+
+ @Override
+ public boolean has(String attrName) {
+ return mParser.getAttributeValue(null, attrName) != null;
+ }
+
+ @Nullable
+ @Override
+ public String getString(String attrName) {
+ return mParser.getAttributeValue(null, attrName);
+ }
+
+ @NonNull
+ @Override
+ public String getString(String attrName, @NonNull String defaultValue) {
+ String value = mParser.getAttributeValue(null, attrName);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ @Override
+ public boolean getBoolean(String attrName) {
+ return getBoolean(attrName, false);
+ }
+
+ @Override
+ public boolean getBoolean(String attrName, boolean defaultValue) {
+ return mParser.getAttributeBoolean(null, attrName, defaultValue);
+ }
+
+ @Override
+ public int getInt(String attrName) {
+ return getInt(attrName, DEFAULT_NUMBER);
+ }
+
+ @Override
+ public int getInt(String attrName, int defaultValue) {
+ return mParser.getAttributeInt(null, attrName, defaultValue);
+ }
+
+ @Override
+ public long getLong(String attrName) {
+ return getLong(attrName, DEFAULT_NUMBER);
+ }
+
+ @Override
+ public long getLong(String attrName, int defaultValue) {
+ return mParser.getAttributeLong(null, attrName, defaultValue);
+ }
+
+ @Override
+ public ChildSection children() {
+ mDepthStack.push(mParser.getDepth());
+ return this;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return moveToNextInternal(null);
+ }
+
+ @Override
+ public boolean moveToNext(@NonNull String expectedChildTagName) {
+ return moveToNextInternal(expectedChildTagName);
+ }
+
+ private boolean moveToNextInternal(@Nullable String expectedChildTagName) {
+ try {
+ int depth = mDepthStack.peek();
+ boolean hasTag = false;
+ int type;
+ while (!hasTag
+ && (type = mParser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || mParser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (expectedChildTagName != null
+ && !expectedChildTagName.equals(mParser.getName())) {
+ continue;
+ }
+
+ hasTag = true;
+ }
+
+ if (!hasTag) {
+ mDepthStack.pop();
+ }
+
+ return hasTag;
+ } catch (Exception ignored) {
+ return false;
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mDepthStack.isEmpty()) {
+ Slog.wtf(TAG, "Children depth stack was not empty, data may have been lost",
+ new Exception());
+ }
+ if (mInput != null) {
+ mInput.close();
+ }
+ }
+ }
+
+ public interface WriteSection extends AutoCloseable {
+
+ WriteSection startSection(@NonNull String sectionName) throws IOException;
+
+ WriteSection attribute(String attrName, @Nullable String value) throws IOException;
+
+ WriteSection attribute(String attrName, int value) throws IOException;
+
+ WriteSection attribute(String attrName, long value) throws IOException;
+
+ WriteSection attribute(String attrName, boolean value) throws IOException;
+
+ @Override
+ void close() throws IOException;
+
+ void finish() throws IOException;
+ }
+
+ private static class WriteSectionImpl implements WriteSection {
+
+ @NonNull
+ private final TypedXmlSerializer mXmlSerializer;
+
+ @NonNull
+ private final Stack<String> mTagStack = new Stack<>();
+
+ private WriteSectionImpl(@NonNull TypedXmlSerializer xmlSerializer) {
+ mXmlSerializer = xmlSerializer;
+ }
+
+ @Override
+ public WriteSection startSection(@NonNull String sectionName) throws IOException {
+ // Try to start the tag first before we push it to the stack
+ mXmlSerializer.startTag(null, sectionName);
+ mTagStack.push(sectionName);
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, String value) throws IOException {
+ if (value != null) {
+ mXmlSerializer.attribute(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, int value) throws IOException {
+ if (value != DEFAULT_NUMBER) {
+ mXmlSerializer.attributeInt(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, long value) throws IOException {
+ if (value != DEFAULT_NUMBER) {
+ mXmlSerializer.attributeLong(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, boolean value) throws IOException {
+ if (value) {
+ mXmlSerializer.attributeBoolean(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public void finish() throws IOException {
+ close();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mXmlSerializer.endTag(null, mTagStack.pop());
+ }
+
+ private void closeCompletely() throws IOException {
+ if (DEBUG_THROW_EXCEPTIONS && mTagStack != null && !mTagStack.isEmpty()) {
+ throw new IllegalStateException(
+ "tag stack is not empty when closing, contains " + mTagStack);
+ } else if (mTagStack != null) {
+ while (!mTagStack.isEmpty()) {
+ close();
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f43240b..a807777 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3880,6 +3880,7 @@
*/
@Override
public boolean removeUser(@UserIdInt int userId) {
+ Slog.i(LOG_TAG, "removeUser u" + userId);
checkManageOrCreateUsersPermission("Only the system can remove users");
final String restriction = getUserRemovalRestriction(userId);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 9dde7df..629f120 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -406,7 +406,8 @@
ParsedProcess proc = procs.get(key);
retProcs.put(proc.getName(),
new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()),
- proc.getGwpAsanMode()));
+ proc.getGwpAsanMode(), proc.getMemtagMode(),
+ proc.getNativeHeapZeroInit()));
}
return retProcs;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 851ddd1..e8be9b6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -19,6 +19,7 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
@@ -33,6 +34,7 @@
import android.os.Build;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.permission.PermissionManager;
import android.util.DisplayMetrics;
import android.util.Slog;
@@ -42,6 +44,7 @@
import com.android.server.pm.parsing.pkg.ParsedPackage;
import java.io.File;
+import java.util.List;
/**
* The v2 of {@link PackageParser} for use when parsing is initiated in the server and must
@@ -118,10 +121,15 @@
displayMetrics.setToDefaults();
}
+ PermissionManager permissionManager = ActivityThread.currentApplication()
+ .getSystemService(PermissionManager.class);
+ List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
+ .getSplitPermissions();
+
mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
parsingUtils = new ParsingPackageUtils(onlyCoreApps, separateProcesses, displayMetrics,
- callback);
+ splitPermissions, callback);
ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
ApplicationInfo appInfo = mSharedAppInfo.get();
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
index 92f22a4..e8eae47 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
@@ -104,6 +104,26 @@
}
/**
+ * Get the permission state for a permission and a user.
+ *
+ * @param permissionName the permission name
+ * @param userId the user ID
+ * @return the permission state
+ *
+ * @hide
+ */
+ @Nullable
+ public PermissionState getPermissionState(@NonNull String permissionName,
+ @UserIdInt int userId) {
+ checkUserId(userId);
+ UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ return null;
+ }
+ return userState.getPermissionState(permissionName);
+ }
+
+ /**
* Put a permission state for a user.
*
* @param permissionState the permission state
@@ -142,10 +162,10 @@
}
/**
- * Get all the runtime permission states for a user.
+ * Get all the permission states for a user.
*
* @param userId the user ID
- * @return the runtime permission states
+ * @return the permission states
*/
@NonNull
public Collection<PermissionState> getPermissionStates(@UserIdInt int userId) {
@@ -301,5 +321,25 @@
public int getFlags() {
return mFlags;
}
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null || getClass() != object.getClass()) {
+ return false;
+ }
+ PermissionState that = (PermissionState) object;
+ return mRuntime == that.mRuntime
+ && mGranted == that.mGranted
+ && mFlags == that.mFlags
+ && Objects.equals(mName, that.mName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mRuntime, mGranted, mFlags);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
new file mode 100644
index 0000000..36efb39
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.Patterns;
+
+import com.android.server.SystemConfig;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.List;
+import java.util.Set;
+
+public class DomainVerificationCollector {
+
+ @NonNull
+ private final PlatformCompat mPlatformCompat;
+
+ @NonNull
+ private final SystemConfig mSystemConfig;
+
+ public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
+ @NonNull SystemConfig systemConfig) {
+ mPlatformCompat = platformCompat;
+ mSystemConfig = systemConfig;
+ }
+
+ /**
+ * With the updated form of the app links verification APIs, an app will be required to declare
+ * domains inside an intent filter which includes all of the following:
+ * <ul>
+ * <li>- android:autoVerify="true"</li>
+ * <li>- Intent.ACTION_VIEW</li>
+ * <li>- Intent.CATEGORY_BROWSABLE</li>
+ * <li>- Intent.CATEGORY_DEFAULT</li>
+ * <li>- Only IntentFilter.SCHEME_HTTP and/or IntentFilter.SCHEME_HTTPS,
+ * with no other schemes</li>
+ * </ul>
+ *
+ * On prior versions of Android, Intent.CATEGORY_BROWSABLE was not a requirement, other
+ * schemes were allowed, and setting autoVerify to true in any intent filter would implicitly
+ * pretend that all intent filters were set to autoVerify="true".
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+ public static final long RESTRICT_DOMAINS = 175408749L;
+
+ @NonNull
+ public ArraySet<String> collectAllWebDomains(@NonNull AndroidPackage pkg) {
+ return collectDomains(pkg, false);
+ }
+
+ /**
+ * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires
+ * {@link IntentFilter#getAutoVerify()} == true.
+ */
+ @NonNull
+ public ArraySet<String> collectAutoVerifyDomains(@NonNull AndroidPackage pkg) {
+ return collectDomains(pkg, true);
+ }
+
+ @NonNull
+ private ArraySet<String> collectDomains(@NonNull AndroidPackage pkg,
+ boolean checkAutoVerify) {
+ boolean restrictDomains =
+ DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS);
+
+ ArraySet<String> domains = new ArraySet<>();
+
+ if (restrictDomains) {
+ collectDomains(domains, pkg, checkAutoVerify);
+ } else {
+ collectDomainsLegacy(domains, pkg, checkAutoVerify);
+ }
+
+ return domains;
+ }
+
+ /** @see #RESTRICT_DOMAINS */
+ private void collectDomainsLegacy(@NonNull Set<String> domains,
+ @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+ if (!checkAutoVerify) {
+ // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2
+ collectDomains(domains, pkg, false);
+ return;
+ }
+
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+
+ // Due to a bug in the platform, for backwards compatibility, assume that all linked apps
+ // require auto verification, even if they forget to mark their manifest as such.
+ boolean needsAutoVerify = mSystemConfig.getLinkedApps().contains(pkg.getPackageName());
+ if (!needsAutoVerify) {
+ for (int activityIndex = 0; activityIndex < activitiesSize && !needsAutoVerify;
+ activityIndex++) {
+ ParsedActivity activity = activities.get(activityIndex);
+ List<ParsedIntentInfo> intents = activity.getIntents();
+ int intentsSize = intents.size();
+ for (int intentIndex = 0; intentIndex < intentsSize && !needsAutoVerify;
+ intentIndex++) {
+ ParsedIntentInfo intent = intents.get(intentIndex);
+ needsAutoVerify = intent.needsVerification();
+ }
+ }
+
+ if (!needsAutoVerify) {
+ return;
+ }
+ }
+
+ for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ ParsedActivity activity = activities.get(activityIndex);
+ List<ParsedIntentInfo> intents = activity.getIntents();
+ int intentsSize = intents.size();
+ for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+ ParsedIntentInfo intent = intents.get(intentIndex);
+ if (intent.handlesWebUris(false)) {
+ int authorityCount = intent.countDataAuthorities();
+ for (int index = 0; index < authorityCount; index++) {
+ domains.add(intent.getDataAuthority(index).getHost());
+ }
+ }
+ }
+ }
+ }
+
+ /** @see #RESTRICT_DOMAINS */
+ private void collectDomains(@NonNull Set<String> domains,
+ @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ ParsedActivity activity = activities.get(activityIndex);
+ List<ParsedIntentInfo> intents = activity.getIntents();
+ int intentsSize = intents.size();
+ for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+ ParsedIntentInfo intent = intents.get(intentIndex);
+ if (checkAutoVerify && !intent.getAutoVerify()) {
+ continue;
+ }
+
+ if (!intent.hasCategory(Intent.CATEGORY_DEFAULT)
+ || !intent.handlesWebUris(checkAutoVerify)) {
+ continue;
+ }
+
+ // TODO(b/159952358): There seems to be no way to associate the exact host
+ // with its scheme, meaning all hosts have to be verified as if they were
+ // web schemes. This means that given the following:
+ // <intent-filter android:autoVerify="true">
+ // ...
+ // <data android:scheme="https" android:host="one.example.com"/>
+ // <data android:scheme="https" android:host="two.example.com"/>
+ // <data android:host="three.example.com"/>
+ // <data android:scheme="nonWeb" android:host="four.example.com"/>
+ // </intent-filter>
+ // The verification agent will be asked to verify four.example.com, which the
+ // app will probably fail. This can be re-configured to work properly by the
+ // app developer by declaring a separate intent-filter. This may not be worth
+ // fixing.
+ int authorityCount = intent.countDataAuthorities();
+ for (int index = 0; index < authorityCount; index++) {
+ String host = intent.getDataAuthority(index).getHost();
+ // It's easy to misconfigure autoVerify intent filters, so to avoid
+ // adding unintended hosts, check if the host is an HTTP domain.
+ if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
+ domains.add(host);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
new file mode 100644
index 0000000..af9978b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.PackageUtils;
+import android.util.SparseArray;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.PackageSetting;
+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.parsing.pkg.AndroidPackage;
+
+import java.util.Arrays;
+
+public class DomainVerificationDebug {
+
+ @NonNull
+ private final DomainVerificationCollector mCollector;
+
+ DomainVerificationDebug(DomainVerificationCollector collector) {
+ mCollector = collector;
+ }
+
+ public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId,
+ @NonNull DomainVerificationService.Connection connection,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> stateMap)
+ throws NameNotFoundException {
+ ArrayMap<String, Integer> reusedMap = new ArrayMap<>();
+ ArraySet<String> reusedSet = new ArraySet<>();
+
+ if (packageName == null) {
+ int size = stateMap.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = stateMap.valueAt(index);
+ String pkgName = pkgState.getPackageName();
+ PackageSetting pkgSetting = connection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+
+ boolean wasHeaderPrinted = printState(writer, pkgState, pkgSetting.getPkg(),
+ reusedMap, false);
+ printState(writer, pkgState, pkgSetting.getPkg(), userId, reusedSet,
+ wasHeaderPrinted);
+ }
+ } else {
+ DomainVerificationPkgState pkgState = stateMap.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ PackageSetting pkgSetting = connection.getPackageSettingLocked(packageName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ AndroidPackage pkg = pkgSetting.getPkg();
+ printState(writer, pkgState, pkg, reusedMap, false);
+ printState(writer, pkgState, pkg, userId, reusedSet, true);
+ }
+ }
+
+ boolean printState(@NonNull IndentingPrintWriter writer,
+ @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+ @NonNull ArrayMap<String, Integer> reusedMap, boolean wasHeaderPrinted) {
+ reusedMap.clear();
+ reusedMap.putAll(pkgState.getStateMap());
+
+ ArraySet<String> declaredDomains = mCollector.collectAutoVerifyDomains(pkg);
+ int declaredSize = declaredDomains.size();
+ for (int declaredIndex = 0; declaredIndex < declaredSize; declaredIndex++) {
+ String domain = declaredDomains.valueAt(declaredIndex);
+ reusedMap.putIfAbsent(domain, DomainVerificationState.STATE_NO_RESPONSE);
+ }
+
+ boolean printedHeader = false;
+
+ if (!reusedMap.isEmpty()) {
+ if (!wasHeaderPrinted) {
+ Signature[] signatures = pkg.getSigningDetails().signatures;
+ String signaturesDigest = signatures == null ? null : Arrays.toString(
+ PackageUtils.computeSignaturesSha256Digests(
+ pkg.getSigningDetails().signatures));
+
+ writer.println(pkgState.getPackageName() + ":");
+ writer.increaseIndent();
+ writer.println("ID: " + pkgState.getId());
+ writer.println("Signatures: " + signaturesDigest);
+ writer.decreaseIndent();
+ printedHeader = true;
+ }
+
+ writer.increaseIndent();
+ writer.println("Domain verification state:");
+ writer.increaseIndent();
+ int stateSize = reusedMap.size();
+ for (int stateIndex = 0; stateIndex < stateSize; stateIndex++) {
+ String domain = reusedMap.keyAt(stateIndex);
+ Integer state = reusedMap.valueAt(stateIndex);
+ writer.print(domain);
+ writer.print(": ");
+ writer.println(DomainVerificationManager.stateToDebugString(state));
+ }
+ writer.decreaseIndent();
+ writer.decreaseIndent();
+ }
+
+ return printedHeader;
+ }
+
+ void printState(@NonNull IndentingPrintWriter writer,
+ @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+ @Nullable @UserIdInt Integer userId, @NonNull ArraySet<String> reusedSet,
+ boolean wasHeaderPrinted) {
+ if (userId == null) {
+ return;
+ }
+
+ ArraySet<String> allWebDomains = mCollector.collectAllWebDomains(pkg);
+ SparseArray<DomainVerificationUserState> userStates =
+ pkgState.getUserSelectionStates();
+ if (userId == UserHandle.USER_ALL) {
+ int size = userStates.size();
+ if (size == 0) {
+ printState(writer, pkgState, userId, null, reusedSet, allWebDomains,
+ wasHeaderPrinted);
+ } else {
+ for (int index = 0; index < size; index++) {
+ DomainVerificationUserState userState = userStates.valueAt(index);
+ printState(writer, pkgState, userState.getUserId(), userState, reusedSet,
+ allWebDomains, wasHeaderPrinted);
+ }
+ }
+ } else {
+ DomainVerificationUserState userState = userStates.get(userId);
+ printState(writer, pkgState, userId, userState, reusedSet, allWebDomains,
+ wasHeaderPrinted);
+ }
+ }
+
+ boolean printState(@NonNull IndentingPrintWriter writer,
+ @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
+ @Nullable DomainVerificationUserState userState, @NonNull ArraySet<String> reusedSet,
+ @NonNull ArraySet<String> allWebDomains, boolean wasHeaderPrinted) {
+ reusedSet.clear();
+ reusedSet.addAll(allWebDomains);
+ if (userState != null) {
+ reusedSet.removeAll(userState.getEnabledHosts());
+ }
+
+ boolean printedHeader = false;
+
+ ArraySet<String> enabledHosts = userState == null ? null : userState.getEnabledHosts();
+ int enabledSize = CollectionUtils.size(enabledHosts);
+ int disabledSize = reusedSet.size();
+ if (enabledSize > 0 || disabledSize > 0) {
+ if (!wasHeaderPrinted) {
+ writer.println(pkgState.getPackageName() + " " + pkgState.getId() + ":");
+ printedHeader = true;
+ }
+
+ boolean isLinkHandlingAllowed = userState == null
+ || !userState.isDisallowLinkHandling();
+
+ writer.increaseIndent();
+ writer.print("User ");
+ writer.print(userId == UserHandle.USER_ALL ? "all" : userId);
+ writer.println(":");
+ writer.increaseIndent();
+ writer.print("Verification link handling allowed: ");
+ writer.println(isLinkHandlingAllowed);
+ writer.println("Selection state:");
+ writer.increaseIndent();
+
+ if (enabledSize > 0) {
+ writer.println("Enabled:");
+ writer.increaseIndent();
+ for (int enabledIndex = 0; enabledIndex < enabledSize; enabledIndex++) {
+ //noinspection ConstantConditions
+ writer.println(enabledHosts.valueAt(enabledIndex));
+ }
+ writer.decreaseIndent();
+ }
+
+ if (disabledSize > 0) {
+ writer.println("Disabled:");
+ writer.increaseIndent();
+ for (int disabledIndex = 0; disabledIndex < disabledSize; disabledIndex++) {
+ writer.println(reusedSet.valueAt(disabledIndex));
+ }
+ writer.decreaseIndent();
+ }
+
+ writer.decreaseIndent();
+ writer.decreaseIndent();
+ writer.decreaseIndent();
+ }
+
+ return printedHeader;
+ }
+}
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
new file mode 100644
index 0000000..c521f82
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Process;
+
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+
+public class DomainVerificationEnforcer {
+
+ @NonNull
+ private final Context mContext;
+
+ public DomainVerificationEnforcer(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Enforced when mutating any state from shell or internally in the system process.
+ */
+ public void assertInternal(int callingUid) {
+ switch (callingUid) {
+ case Process.ROOT_UID:
+ case Process.SHELL_UID:
+ case Process.SYSTEM_UID:
+ break;
+ default:
+ throw new SecurityException(
+ "Caller " + callingUid + " is not allowed to change internal state");
+ }
+ }
+
+ /**
+ * Enforced when retrieving state for a package. The system, the verifier, and anyone approved
+ * to mutate user selections are allowed through.
+ */
+ public void assertApprovedQuerent(int callingUid, @NonNull DomainVerificationProxy proxy) {
+ switch (callingUid) {
+ case Process.ROOT_UID:
+ case Process.SHELL_UID:
+ case Process.SYSTEM_UID:
+ break;
+ default:
+ if (!proxy.isCallerVerifier(callingUid)) {
+ mContext.enforcePermission(
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
+ Binder.getCallingPid(), callingUid,
+ "Caller " + callingUid
+ + " is not allowed to query domain verification state");
+ }
+ break;
+ }
+ }
+
+ /**
+ * Enforced when mutating domain verification state inside an exposed API method.
+ */
+ public void assertApprovedVerifier(int callingUid, @NonNull DomainVerificationProxy proxy)
+ throws SecurityException {
+ boolean isAllowed;
+ switch (callingUid) {
+ case Process.ROOT_UID:
+ case Process.SHELL_UID:
+ case Process.SYSTEM_UID:
+ 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");
+ isAllowed = proxy.isCallerVerifier(callingUid);
+ break;
+ }
+
+ if (!isAllowed) {
+ throw new SecurityException("Caller " + callingUid
+ + " is not the approved domain verification agent, isVerifier = "
+ + proxy.isCallerVerifier(callingUid));
+ }
+ }
+
+ /**
+ * Enforced when mutating user selection state inside an exposed API method.
+ */
+ public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
+ @UserIdInt int targetUserId) throws SecurityException {
+ if (callingUserId != targetUserId) {
+ mContext.enforcePermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit other users");
+ }
+
+ mContext.enforcePermission(
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit user selections");
+ }
+
+ public void callerIsLegacyUserSelector(int callingUid) {
+ mContext.enforcePermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit user state");
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
new file mode 100644
index 0000000..c787356
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
@@ -0,0 +1,228 @@
+/*
+ * 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.SparseIntArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.SettingsXml;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Reads and writes the old {@link android.content.pm.IntentFilterVerificationInfo} so that it can
+ * be migrated in to the new API. Will throw away the state once it's successfully applied so that
+ * eventually there will be no legacy state on the device.
+ *
+ * This attempt is best effort, and if the legacy state is lost that's acceptable. The user setting
+ * in the legacy API may have been set incorrectly because it was never made obvious to the user
+ * what it actually toggled, so there's a strong argument to prevent migration anyways. The user
+ * can just set their preferences again, this time with finer grained control, if the legacy state
+ * gets dropped.
+ */
+public class DomainVerificationLegacySettings {
+
+ public static final String TAG_DOMAIN_VERIFICATIONS_LEGACY = "domain-verifications-legacy";
+ public static final String TAG_USER_STATES = "user-states";
+ public static final String ATTR_PACKAGE_NAME = "packageName";
+ public static final String TAG_USER_STATE = "user-state";
+ public static final String ATTR_USER_ID = "userId";
+ public static final String ATTR_STATE = "state";
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @NonNull
+ private final ArrayMap<String, LegacyState> mStates = new ArrayMap<>();
+
+ public void add(@NonNull String packageName, @NonNull IntentFilterVerificationInfo info) {
+ synchronized (mLock) {
+ getOrCreateStateLocked(packageName).setInfo(info);
+ }
+ }
+
+ public void add(@NonNull String packageName, @UserIdInt int userId, int state) {
+ synchronized (mLock) {
+ getOrCreateStateLocked(packageName).addUserState(userId, state);
+ }
+ }
+
+ public int getUserState(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ LegacyState state = mStates.get(packageName);
+ if (state != null) {
+ return state.getUserState(userId);
+ }
+ }
+ return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ @Nullable
+ public SparseIntArray getUserStates(@NonNull String packageName) {
+ synchronized (mLock) {
+ LegacyState state = mStates.get(packageName);
+ if (state != null) {
+ // Yes, this returns outside of the lock, but we assume that retrieval generally
+ // only happens after all adding has concluded from reading settings.
+ return state.getUserStates();
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public IntentFilterVerificationInfo remove(@NonNull String packageName) {
+ synchronized (mLock) {
+ LegacyState state = mStates.get(packageName);
+ if (state != null && !state.isAttached()) {
+ state.markAttached();
+ return state.getInfo();
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private LegacyState getOrCreateStateLocked(@NonNull String packageName) {
+ LegacyState state = mStates.get(packageName);
+ if (state == null) {
+ state = new LegacyState();
+ mStates.put(packageName, state);
+ }
+
+ return state;
+ }
+
+ public void writeSettings(TypedXmlSerializer xmlSerializer) throws IOException {
+ try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
+ try (SettingsXml.WriteSection ignored =
+ serializer.startSection(TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
+ synchronized (mLock) {
+ final int statesSize = mStates.size();
+ for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
+ final LegacyState state = mStates.valueAt(stateIndex);
+ final SparseIntArray userStates = state.getUserStates();
+ if (userStates == null) {
+ continue;
+ }
+
+ final String packageName = mStates.keyAt(stateIndex);
+ try (SettingsXml.WriteSection userStatesSection =
+ serializer.startSection(TAG_USER_STATES)
+ .attribute(ATTR_PACKAGE_NAME, packageName)) {
+ final int userStatesSize = userStates.size();
+ for (int userStateIndex = 0; userStateIndex < userStatesSize;
+ userStateIndex++) {
+ final int userId = userStates.keyAt(userStateIndex);
+ final int userState = userStates.valueAt(userStateIndex);
+ userStatesSection.startSection(TAG_USER_STATE)
+ .attribute(ATTR_USER_ID, userId)
+ .attribute(ATTR_STATE, userState)
+ .finish();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void readSettings(TypedXmlPullParser xmlParser)
+ throws IOException, XmlPullParserException {
+ final SettingsXml.ChildSection child = SettingsXml.parser(xmlParser).children();
+ while (child.moveToNext()) {
+ if (TAG_USER_STATES.equals(child.getName())) {
+ readUserStates(child);
+ }
+ }
+ }
+
+ private void readUserStates(SettingsXml.ReadSection section) {
+ String packageName = section.getString(ATTR_PACKAGE_NAME);
+ synchronized (mLock) {
+ final LegacyState legacyState = getOrCreateStateLocked(packageName);
+ final SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext()) {
+ if (TAG_USER_STATE.equals(child.getName())) {
+ readUserState(child, legacyState);
+ }
+ }
+ }
+ }
+
+ private void readUserState(SettingsXml.ReadSection section, LegacyState legacyState) {
+ int userId = section.getInt(ATTR_USER_ID);
+ int state = section.getInt(ATTR_STATE);
+ legacyState.addUserState(userId, state);
+ }
+
+ static class LegacyState {
+ @Nullable
+ private IntentFilterVerificationInfo mInfo;
+
+ @Nullable
+ private SparseIntArray mUserStates;
+
+ private boolean attached;
+
+ @Nullable
+ public IntentFilterVerificationInfo getInfo() {
+ return mInfo;
+ }
+
+ public int getUserState(int userId) {
+ return mUserStates.get(userId,
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
+ }
+
+ @Nullable
+ public SparseIntArray getUserStates() {
+ return mUserStates;
+ }
+
+ public void setInfo(@NonNull IntentFilterVerificationInfo info) {
+ mInfo = info;
+ }
+
+ public void addUserState(@UserIdInt int userId, int state) {
+ if (mUserStates == null) {
+ mUserStates = new SparseIntArray(1);
+ }
+ mUserStates.put(userId, state);
+ }
+
+ public boolean isAttached() {
+ return attached;
+ }
+
+ public void markAttached() {
+ attached = true;
+ }
+ }
+}
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
new file mode 100644
index 0000000..7ad275a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.content.Intent;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.UUID;
+
+public interface DomainVerificationManagerInternal extends DomainVerificationManager {
+
+ UUID DISABLED_ID = new UUID(0, 0);
+
+ /**
+ * Generate a new domain set ID to be used for attaching new packages.
+ */
+ @NonNull
+ UUID generateNewId();
+
+ void setConnection(@NonNull Connection connection);
+
+ @NonNull
+ DomainVerificationProxy getProxy();
+
+ /**
+ * Update the proxy implementation that talks to the domain verification agent on device. The
+ * default proxy is a stub that does nothing, and broadcast functionality will only work once a
+ * real implementation is attached.
+ */
+ void setProxy(@NonNull DomainVerificationProxy proxy);
+
+ /**
+ * @see DomainVerificationProxy.BaseConnection#runMessage(int, Object)
+ */
+ boolean runMessage(int messageCode, Object object);
+
+ /**
+ * Restores or creates internal state for the new package. This can either be from scanning a
+ * package at boot, or a truly new installation on the device. It is expected that the {@link
+ * PackageSetting#getDomainSetId()} already be set to the correct value.
+ * <p>
+ * If this is from scan, there should be a pending state that was previous read using {@link
+ * #readSettings(TypedXmlPullParser)}, which will be attached as-is to the package. In this
+ * case, a broadcast will not be sent to the domain verification agent on device, as it is
+ * assumed nothing has changed since the device rebooted.
+ * <p>
+ * If this is a new install, state will be restored from a previous call to {@link
+ * #restoreSettings(TypedXmlPullParser)}, or a new one will be generated. In either case, a
+ * broadcast will be sent to the domain verification agent so it may re-run any verification
+ * logic for the newly associated domains.
+ * <p>
+ * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
+ * lock. This should never be called from within the domain verification classes themselves.
+ * <p>
+ * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
+ * caller.
+ */
+ void addPackage(@NonNull PackageSetting newPkgSetting);
+
+ /**
+ * Migrates verification state from a previous install to a new one. It is expected that the
+ * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from
+ * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the
+ * assumption that the new package will pass the same server side config as the previous
+ * package, as they have matching signatures.
+ * <p>
+ * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
+ * lock. This should never be called from within the domain verification classes themselves.
+ * <p>
+ * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
+ * caller.
+ */
+ void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting);
+
+ /**
+ * Serializes the entire internal state. This is equivalent to a full backup of the existing
+ * verification state. This write includes legacy state, as a sibling tag the modern state.
+ */
+ void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException;
+
+ /**
+ * Read back a list of {@link DomainVerificationPkgState}s previously written by {@link
+ * #writeSettings(TypedXmlSerializer)}. Assumes that the
+ * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} tag has already been entered.
+ * <p>
+ * This is expected to only be used to re-attach states for packages already known to be on the
+ * device. If restoring from a backup, use {@link #restoreSettings(TypedXmlPullParser)}.
+ */
+ void readSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+
+ /**
+ * Read back data from
+ * {@link DomainVerificationLegacySettings#writeSettings(TypedXmlSerializer)}. Assumes that the
+ * {@link DomainVerificationLegacySettings#TAG_DOMAIN_VERIFICATIONS_LEGACY} tag has already
+ * been entered.
+ */
+ void readLegacySettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+
+ /**
+ * Remove all state for the given package.
+ */
+ void clearPackage(@NonNull String packageName);
+
+ /**
+ * Delete all the state for a user. This can be because the user has been removed from the
+ * device, or simply that the state for a user should be deleted.
+ */
+ void clearUser(@UserIdInt int userId);
+
+ /**
+ * Restore a list of {@link DomainVerificationPkgState}s previously written by {@link
+ * #writeSettings(TypedXmlSerializer)}. Assumes that the
+ * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS}
+ * tag has already been entered.
+ * <p>
+ * This is <b>only</b> for restore, and will override package states, ignoring if their {@link
+ * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains marked
+ * as success verify against the server correctly, although the verification agent may decide to
+ * re-verify them when it gets the chance.
+ */
+ /*
+ * TODO(b/170746586): Figure out how to verify that package signatures match at snapshot time
+ * and restore time.
+ */
+ void restoreSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+
+ /**
+ * Set aside a legacy {@link IntentFilterVerificationInfo} that will be restored to a pending
+ * {@link DomainVerificationPkgState} once it's added through
+ * {@link #addPackage(PackageSetting)}.
+ */
+ void addLegacySetting(@NonNull String packageName, @NonNull IntentFilterVerificationInfo info);
+
+ /**
+ * Set aside a legacy user selection that will be restored to a pending
+ * {@link DomainVerificationPkgState} once it's added through
+ * {@link #addPackage(PackageSetting)}.
+ */
+ void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
+
+ /**
+ * Until the legacy APIs are entirely removed, returns the legacy state from the previously
+ * written info stored in {@link com.android.server.pm.Settings}.
+ */
+ 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 specified
+ * @param userId the specific user to print, or null to skip printing user selection
+ * states, supports {@link android.os.UserHandle#USER_ALL}
+ */
+ void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
+
+ @NonNull
+ DomainVerificationShell getShell();
+
+ @NonNull
+ DomainVerificationCollector getCollector();
+
+ /**
+ * Check if a resolving URI is approved to takeover the domain as the sole resolved target.
+ * This can be because the domain was auto-verified for the package, or if the user manually
+ * chose to enable the domain for the package.
+ */
+ boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+ @UserIdInt int userId);
+
+ /**
+ * @return the domain verification set ID for the given package, or null if the ID is
+ * unavailable
+ */
+ @Nullable
+ UUID getDomainVerificationInfoId(@NonNull String packageName);
+
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+ @NonNull Set<String> domains, int state)
+ throws IllegalArgumentException, NameNotFoundException;
+
+
+ interface Connection {
+
+ /**
+ * Notify that a settings change has been made and that eventually
+ * {@link #writeSettings(TypedXmlSerializer)} should be invoked by the parent.
+ */
+ void scheduleWriteSettings();
+
+ /**
+ * Delegate to {@link Binder#getCallingUid()} to allow mocking in tests.
+ */
+ int getCallingUid();
+
+ /**
+ * Delegate to {@link UserHandle#getCallingUserId()} to allow mocking in tests.
+ */
+ @UserIdInt
+ int getCallingUserId();
+
+ /**
+ * @see DomainVerificationProxy.BaseConnection#schedule(int, java.lang.Object)
+ */
+ void schedule(int code, @Nullable Object object);
+
+ @Nullable
+ PackageSetting getPackageSettingLocked(@NonNull String pkgName);
+
+ @Nullable
+ AndroidPackage getPackageLocked(@NonNull String pkgName);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
new file mode 100644
index 0000000..8aa6337
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
+import android.content.pm.verify.domain.DomainVerificationManagerImpl;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.ServiceSpecificException;
+import android.util.ArraySet;
+
+import java.util.List;
+import java.util.UUID;
+
+class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
+
+ @NonNull
+ private DomainVerificationService mService;
+
+ DomainVerificationManagerStub(DomainVerificationService service) {
+ mService = service;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getValidVerificationPackageNames() {
+ try {
+ return mService.getValidVerificationPackageNames();
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationInfo getDomainVerificationInfo(String packageName) {
+ try {
+ return mService.getDomainVerificationInfo(packageName);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationStatus(String domainSetId, List<String> domains,
+ int state) {
+ try {
+ mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
+ new ArraySet<>(domains), state);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed,
+ @UserIdInt int userId) {
+ try {
+ mService.setDomainVerificationLinkHandlingAllowed(packageName, allowed, userId);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationUserSelection(String domainSetId, List<String> domains,
+ boolean enabled, @UserIdInt int userId) {
+ try {
+ mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
+ new ArraySet<>(domains), enabled, userId);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ String packageName, @UserIdInt int userId) {
+ try {
+ return mService.getDomainVerificationUserSelection(packageName, userId);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ private RuntimeException rethrow(Exception exception) throws RuntimeException {
+ if (exception instanceof InvalidDomainSetException) {
+ int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET;
+ packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16;
+ return new ServiceSpecificException(packedErrorCode,
+ ((InvalidDomainSetException) exception).getPackageName());
+ } else if (exception instanceof NameNotFoundException) {
+ return new ServiceSpecificException(
+ DomainVerificationManagerImpl.ERROR_NAME_NOT_FOUND);
+ } else if (exception instanceof RuntimeException) {
+ return (RuntimeException) exception;
+ } else {
+ return new RuntimeException(exception);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java
new file mode 100644
index 0000000..7af78c6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java
@@ -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.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.os.Handler;
+
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+
+/**
+ * Codes that are sent through the {@link PackageManagerService} {@link Handler} and eventually
+ * delegated to {@link DomainVerificationService} and {@link DomainVerificationProxy}.
+ *
+ * These codes are wrapped and thus exclusive to the domain verification APIs. They do not have be
+ * distinct from any of the codes inside {@link PackageManagerService}.
+ */
+public final class DomainVerificationMessageCodes {
+
+ public static final int SEND_REQUEST = 1;
+ public static final int LEGACY_SEND_REQUEST = 2;
+ public static final int LEGACY_ON_INTENT_FILTER_VERIFIED = 3;
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
new file mode 100644
index 0000000..679f948
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.server.pm.SettingsXml;
+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 org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.UUID;
+
+public class DomainVerificationPersistence {
+
+ private static final String TAG = "DomainVerificationPersistence";
+
+ public static final String TAG_DOMAIN_VERIFICATIONS = "domain-verifications";
+ public static final String TAG_ACTIVE = "active";
+ public static final String TAG_RESTORED = "restored";
+
+ public static final String TAG_PACKAGE_STATE = "package-state";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
+ private static final String TAG_USER_STATES = "user-states";
+
+ public static final String TAG_USER_STATE = "user-state";
+ public static final String ATTR_USER_ID = "userId";
+ public static final String ATTR_DISALLOW_LINK_HANDLING = "disallowLinkHandling";
+ public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
+ public static final String TAG_HOST = "host";
+
+ private static final String TAG_STATE = "state";
+ public static final String TAG_DOMAIN = "domain";
+ public static final String ATTR_NAME = "name";
+ public static final String ATTR_STATE = "state";
+
+ public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> pending,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> restored) throws IOException {
+ try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
+ try (SettingsXml.WriteSection ignored = serializer.startSection(
+ TAG_DOMAIN_VERIFICATIONS)) {
+ // Both attached and pending states are written to the active set, since both
+ // should be restored when the device reboots or runs a backup. They're merged into
+ // the same list because at read time the distinction isn't relevant. The pending
+ // list should generally be empty at this point anyways.
+ ArraySet<DomainVerificationPkgState> active = new ArraySet<>();
+
+ int attachedSize = attached.size();
+ for (int attachedIndex = 0; attachedIndex < attachedSize; attachedIndex++) {
+ active.add(attached.valueAt(attachedIndex));
+ }
+
+ int pendingSize = pending.size();
+ for (int pendingIndex = 0; pendingIndex < pendingSize; pendingIndex++) {
+ active.add(pending.valueAt(pendingIndex));
+ }
+
+ try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
+ writePackageStates(activeSection, active);
+ }
+
+ try (SettingsXml.WriteSection restoredSection = serializer.startSection(
+ TAG_RESTORED)) {
+ writePackageStates(restoredSection, restored.values());
+ }
+ }
+ }
+ }
+
+ private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
+ @NonNull Collection<DomainVerificationPkgState> states) throws IOException {
+ if (states.isEmpty()) {
+ return;
+ }
+
+ for (DomainVerificationPkgState state : states) {
+ writePkgStateToXml(section, state);
+ }
+ }
+
+ @NonNull
+ public static ReadResult readFromXml(@NonNull TypedXmlPullParser parentParser)
+ throws IOException, XmlPullParserException {
+ ArrayMap<String, DomainVerificationPkgState> active = new ArrayMap<>();
+ ArrayMap<String, DomainVerificationPkgState> restored = new ArrayMap<>();
+
+ SettingsXml.ChildSection child = SettingsXml.parser(parentParser).children();
+ while (child.moveToNext()) {
+ switch (child.getName()) {
+ case TAG_ACTIVE:
+ readPackageStates(child, active);
+ break;
+ case TAG_RESTORED:
+ readPackageStates(child, restored);
+ break;
+ }
+ }
+
+ return new ReadResult(active, restored);
+ }
+
+ private static void readPackageStates(@NonNull SettingsXml.ReadSection section,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> map) {
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_PACKAGE_STATE)) {
+ DomainVerificationPkgState pkgState = createPkgStateFromXml(child);
+ if (pkgState != null) {
+ // State is unique by package name
+ map.put(pkgState.getPackageName(), pkgState);
+ }
+ }
+ }
+
+ /**
+ * Reads a package state from XML. Assumes the starting {@link #TAG_PACKAGE_STATE} has already
+ * been entered.
+ */
+ @Nullable
+ public static DomainVerificationPkgState createPkgStateFromXml(
+ @NonNull SettingsXml.ReadSection section) {
+ String packageName = section.getString(ATTR_PACKAGE_NAME);
+ String idString = section.getString(ATTR_ID);
+ boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
+ if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
+ return null;
+ }
+ UUID id = UUID.fromString(idString);
+
+ final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
+ final SparseArray<DomainVerificationUserState> userStates = new SparseArray<>();
+
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext()) {
+ switch (child.getName()) {
+ case TAG_STATE:
+ readDomainStates(child, stateMap);
+ break;
+ case TAG_USER_STATES:
+ readUserStates(child, userStates);
+ break;
+ }
+ }
+
+ return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
+ userStates);
+ }
+
+ private static void readUserStates(@NonNull SettingsXml.ReadSection section,
+ @NonNull SparseArray<DomainVerificationUserState> userStates) {
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_USER_STATE)) {
+ DomainVerificationUserState userState = createUserStateFromXml(child);
+ if (userState != null) {
+ userStates.put(userState.getUserId(), userState);
+ }
+ }
+ }
+
+ private static void readDomainStates(@NonNull SettingsXml.ReadSection stateSection,
+ @NonNull ArrayMap<String, Integer> stateMap) {
+ SettingsXml.ChildSection child = stateSection.children();
+ while (child.moveToNext(TAG_DOMAIN)) {
+ String name = child.getString(ATTR_NAME);
+ int state = child.getInt(ATTR_STATE, DomainVerificationState.STATE_NO_RESPONSE);
+ stateMap.put(name, state);
+ }
+ }
+
+ public static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull DomainVerificationPkgState pkgState) throws IOException {
+ try (SettingsXml.WriteSection ignored =
+ parentSection.startSection(TAG_PACKAGE_STATE)
+ .attribute(ATTR_PACKAGE_NAME, pkgState.getPackageName())
+ .attribute(ATTR_ID, pkgState.getId().toString())
+ .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
+ pkgState.isHasAutoVerifyDomains())) {
+ writeStateMap(parentSection, pkgState.getStateMap());
+ writeUserStates(parentSection, pkgState.getUserSelectionStates());
+ }
+ }
+
+ private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull SparseArray<DomainVerificationUserState> states) throws IOException {
+ int size = states.size();
+ if (size == 0) {
+ return;
+ }
+
+ try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) {
+ for (int index = 0; index < size; index++) {
+ writeUserStateToXml(section, states.valueAt(index));
+ }
+ }
+ }
+
+ private static void writeStateMap(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull ArrayMap<String, Integer> stateMap) throws IOException {
+ if (stateMap.isEmpty()) {
+ return;
+ }
+
+ try (SettingsXml.WriteSection stateSection = parentSection.startSection(TAG_STATE)) {
+ int size = stateMap.size();
+ for (int index = 0; index < size; index++) {
+ stateSection.startSection(TAG_DOMAIN)
+ .attribute(ATTR_NAME, stateMap.keyAt(index))
+ .attribute(ATTR_STATE, stateMap.valueAt(index))
+ .finish();
+ }
+ }
+ }
+
+ /**
+ * Reads a user state from XML. Assumes the starting {@link #TAG_USER_STATE} has already been
+ * entered.
+ */
+ @Nullable
+ public static DomainVerificationUserState createUserStateFromXml(
+ @NonNull SettingsXml.ReadSection section) {
+ int userId = section.getInt(ATTR_USER_ID);
+ if (userId == -1) {
+ return null;
+ }
+
+ boolean disallowLinkHandling = section.getBoolean(ATTR_DISALLOW_LINK_HANDLING);
+ ArraySet<String> enabledHosts = new ArraySet<>();
+
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_ENABLED_HOSTS)) {
+ readEnabledHosts(child, enabledHosts);
+ }
+
+ return new DomainVerificationUserState(userId, enabledHosts, disallowLinkHandling);
+ }
+
+ private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
+ @NonNull ArraySet<String> enabledHosts) {
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_HOST)) {
+ String hostName = child.getString(ATTR_NAME);
+ if (!TextUtils.isEmpty(hostName)) {
+ enabledHosts.add(hostName);
+ }
+ }
+ }
+
+ public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull DomainVerificationUserState userState) throws IOException {
+ try (SettingsXml.WriteSection section =
+ parentSection.startSection(TAG_USER_STATE)
+ .attribute(ATTR_USER_ID, userState.getUserId())
+ .attribute(ATTR_DISALLOW_LINK_HANDLING,
+ userState.isDisallowLinkHandling())) {
+ ArraySet<String> enabledHosts = userState.getEnabledHosts();
+ if (!enabledHosts.isEmpty()) {
+ try (SettingsXml.WriteSection enabledHostsSection =
+ section.startSection(TAG_ENABLED_HOSTS)) {
+ int size = enabledHosts.size();
+ for (int index = 0; index < size; index++) {
+ enabledHostsSection.startSection(TAG_HOST)
+ .attribute(ATTR_NAME, enabledHosts.valueAt(index))
+ .finish();
+ }
+ }
+ }
+ }
+ }
+
+ public static class ReadResult {
+
+ @NonNull
+ public final ArrayMap<String, DomainVerificationPkgState> active;
+
+ @NonNull
+ public final ArrayMap<String, DomainVerificationPkgState> restored;
+
+ public ReadResult(@NonNull ArrayMap<String, DomainVerificationPkgState> active,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> restored) {
+ this.active = active;
+ this.restored = restored;
+ }
+ }
+}
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
new file mode 100644
index 0000000..53540c8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -0,0 +1,1241 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.SystemConfig;
+import com.android.server.SystemService;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.PackageSetting;
+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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+public class DomainVerificationService extends SystemService
+ implements DomainVerificationManagerInternal, DomainVerificationShell.Callback {
+
+ private static final String TAG = "DomainVerificationService";
+
+ public static final boolean DEBUG_APPROVAL = true;
+
+ /**
+ * The new user preference API for verifying domains marked autoVerify=true in
+ * AndroidManifest.xml intent filters is not yet implemented in the current platform preview.
+ * This is anticipated to ship before S releases.
+ *
+ * For now, it is possible to preview the new user preference changes by enabling this
+ * ChangeId and using the <code>adb shell pm set-app-links-user-selection</code> and similar
+ * commands.
+ */
+ @ChangeId
+ @Disabled
+ private static final long SETTINGS_API_V2 = 178111421;
+
+ /**
+ * States that are currently alive and attached to a package. Entries are exclusive with the
+ * state stored in {@link DomainVerificationSettings}, as any pending/restored state should be
+ * immediately attached once its available.
+ * <p>
+ * Generally this should be not accessed directly. Prefer calling {@link
+ * #getAndValidateAttachedLocked(UUID, Set, boolean)}.
+ *
+ * @see #getAndValidateAttachedLocked(UUID, Set, boolean)
+ **/
+ @GuardedBy("mLock")
+ @NonNull
+ private final DomainVerificationStateMap<DomainVerificationPkgState> mAttachedPkgStates =
+ new DomainVerificationStateMap<>();
+
+ /**
+ * Lock for all state reads/writes.
+ */
+ private final Object mLock = new Object();
+
+ @NonNull
+ private Connection mConnection;
+
+ @NonNull
+ private final SystemConfig mSystemConfig;
+
+ @NonNull
+ private final PlatformCompat mPlatformCompat;
+
+ @NonNull
+ private final DomainVerificationSettings mSettings;
+
+ @NonNull
+ private final DomainVerificationCollector mCollector;
+
+ @NonNull
+ private final DomainVerificationEnforcer mEnforcer;
+
+ @NonNull
+ private final DomainVerificationDebug mDebug;
+
+ @NonNull
+ private final DomainVerificationShell mShell;
+
+ @NonNull
+ private final DomainVerificationLegacySettings mLegacySettings;
+
+ @NonNull
+ private final IDomainVerificationManager.Stub mStub = new DomainVerificationManagerStub(this);
+
+ @NonNull
+ private DomainVerificationProxy mProxy = new DomainVerificationProxyUnavailable();
+
+ public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig,
+ @NonNull PlatformCompat platformCompat) {
+ super(context);
+ mSystemConfig = systemConfig;
+ mPlatformCompat = platformCompat;
+ mSettings = new DomainVerificationSettings();
+ mCollector = new DomainVerificationCollector(platformCompat, systemConfig);
+ mEnforcer = new DomainVerificationEnforcer(context);
+ mDebug = new DomainVerificationDebug(mCollector);
+ mShell = new DomainVerificationShell(this);
+ mLegacySettings = new DomainVerificationLegacySettings();
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.DOMAIN_VERIFICATION_SERVICE, mStub);
+ }
+
+ @Override
+ public void setConnection(@NonNull Connection connection) {
+ mConnection = connection;
+ }
+
+ @NonNull
+ @Override
+ public DomainVerificationProxy getProxy() {
+ return mProxy;
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+ if (phase != SystemService.PHASE_BOOT_COMPLETED || !hasRealVerifier()) {
+ return;
+ }
+
+ verifyPackages(null, false);
+ }
+
+ @Override
+ public void onUserUnlocked(@NonNull TargetUser user) {
+ super.onUserUnlocked(user);
+
+ // Package verification is sent at both boot and user unlock. The latter will allow v1
+ // verification agents to respond to the request, since they will not be directBootAware.
+ // However, ideally v2 implementations are boot aware and can handle the initial boot
+ // broadcast, to start verifying packages as soon as possible. It's possible this causes
+ // unnecessary duplication at device start up, but the implementation is responsible for
+ // de-duplicating.
+ // TODO: This can be improved by checking if the broadcast was received by the
+ // verification agent in the initial boot broadcast
+ verifyPackages(null, false);
+ }
+
+ @Override
+ public void setProxy(@NonNull DomainVerificationProxy proxy) {
+ mProxy = proxy;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getValidVerificationPackageNames() {
+ mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy);
+ List<String> packageNames = new ArrayList<>();
+ synchronized (mLock) {
+ int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ if (pkgState.isHasAutoVerifyDomains()) {
+ packageNames.add(pkgState.getPackageName());
+ }
+ }
+ }
+ return packageNames;
+ }
+
+ @Nullable
+ @Override
+ public UUID getDomainVerificationInfoId(@NonNull String packageName) {
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState != null) {
+ return pkgState.getId();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException {
+ mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ return null;
+ }
+
+ AndroidPackage pkg = mConnection.getPackageLocked(packageName);
+ if (pkg == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
+
+ // TODO(b/159952358): Should the domain list be cached?
+ ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+ if (domains.isEmpty()) {
+ return null;
+ }
+
+ int size = domains.size();
+ for (int index = 0; index < size; index++) {
+ hostToStateMap.putIfAbsent(domains.valueAt(index),
+ DomainVerificationState.STATE_NO_RESPONSE);
+ }
+
+ // TODO(b/159952358): Do not return if no values are editable (all ignored states)?
+ return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ int state) throws InvalidDomainSetException, NameNotFoundException {
+ if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
+ if (state != DomainVerificationState.STATE_SUCCESS) {
+ throw new IllegalArgumentException(
+ "Verifier can only set STATE_SUCCESS or codes greater than or equal to "
+ + "STATE_FIRST_VERIFIER_DEFINED");
+ }
+ }
+
+ setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, domains,
+ state);
+ }
+
+ @Override
+ public void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+ @NonNull Set<String> domains, int state)
+ throws InvalidDomainSetException, NameNotFoundException {
+ mEnforcer.assertApprovedVerifier(callingUid, mProxy);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+ true /* forAutoVerify */);
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ for (String domain : domains) {
+ Integer previousState = stateMap.get(domain);
+ if (previousState != null
+ && !DomainVerificationManager.isStateModifiable(previousState)) {
+ continue;
+ }
+
+ stateMap.put(domain, state);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationStatusInternal(@Nullable String packageName, int state,
+ @Nullable ArraySet<String> domains) throws NameNotFoundException {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "State must be one of NO_RESPONSE, SUCCESS, APPROVED, or DENIED");
+ }
+
+ if (packageName == null) {
+ synchronized (mLock) {
+ ArraySet<String> validDomains = new ArraySet<>();
+
+ int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ String pkgName = pkgState.getPackageName();
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+
+ AndroidPackage pkg = pkgSetting.getPkg();
+
+ validDomains.clear();
+
+ ArraySet<String> autoVerifyDomains = mCollector.collectAutoVerifyDomains(pkg);
+ if (domains == null) {
+ validDomains.addAll(autoVerifyDomains);
+ } else {
+ validDomains.addAll(domains);
+ validDomains.retainAll(autoVerifyDomains);
+ }
+
+ setDomainVerificationStatusInternal(pkgState, state, validDomains);
+ }
+ }
+ } else {
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ AndroidPackage pkg = pkgSetting.getPkg();
+ if (domains == null) {
+ domains = mCollector.collectAutoVerifyDomains(pkg);
+ } else {
+ domains.retainAll(mCollector.collectAutoVerifyDomains(pkg));
+ }
+
+ setDomainVerificationStatusInternal(pkgState, state, domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ private void setDomainVerificationStatusInternal(@NonNull DomainVerificationPkgState pkgState,
+ int state, @NonNull ArraySet<String> validDomains) {
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int size = validDomains.size();
+ for (int index = 0; index < size; index++) {
+ stateMap.put(validDomains.valueAt(index), state);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed) throws NameNotFoundException {
+ setDomainVerificationLinkHandlingAllowed(packageName, allowed,
+ mConnection.getCallingUserId());
+ }
+
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
+ mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), userId);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ pkgState.getOrCreateUserSelectionState(userId)
+ .setDisallowLinkHandling(!allowed);
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowedInternal(@Nullable String packageName,
+ boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+ if (packageName == null) {
+ synchronized (mLock) {
+ int pkgStateSize = mAttachedPkgStates.size();
+ for (int pkgStateIndex = 0; pkgStateIndex < pkgStateSize; pkgStateIndex++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
+ if (userId == UserHandle.USER_ALL) {
+ SparseArray<DomainVerificationUserState> userStates =
+ pkgState.getUserSelectionStates();
+ int userStatesSize = userStates.size();
+ for (int userStateIndex = 0; userStateIndex < userStatesSize;
+ userStateIndex++) {
+ userStates.valueAt(userStateIndex)
+ .setDisallowLinkHandling(!allowed);
+ }
+ } else {
+ pkgState.getOrCreateUserSelectionState(userId)
+ .setDisallowLinkHandling(!allowed);
+ }
+ }
+
+ }
+ } else {
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ pkgState.getOrCreateUserSelectionState(userId)
+ .setDisallowLinkHandling(!allowed);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled)
+ throws InvalidDomainSetException, NameNotFoundException {
+ setDomainVerificationUserSelection(domainSetId, domains, enabled,
+ mConnection.getCallingUserId());
+ }
+
+ 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) {
+ DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+ false /* forAutoVerify */);
+ DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+ if (enabled) {
+ userState.addHosts(domains);
+ } else {
+ userState.removeHosts(domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
+ @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+ throws NameNotFoundException {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+
+ if (packageName == null) {
+ synchronized (mLock) {
+ Set<String> validDomains = new ArraySet<>();
+
+ int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ String pkgName = pkgState.getPackageName();
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+
+ validDomains.clear();
+ validDomains.addAll(domains);
+
+ setDomainVerificationUserSelectionInternal(userId, pkgState,
+ pkgSetting.getPkg(), enabled, validDomains);
+ }
+ }
+ } else {
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ setDomainVerificationUserSelectionInternal(userId, pkgState, pkgSetting.getPkg(),
+ enabled, domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ private void setDomainVerificationUserSelectionInternal(int userId,
+ @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+ boolean enabled, Set<String> domains) {
+ domains.retainAll(mCollector.collectAllWebDomains(pkg));
+
+ SparseArray<DomainVerificationUserState> userStates =
+ pkgState.getUserSelectionStates();
+ if (userId == UserHandle.USER_ALL) {
+ int size = userStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationUserState userState = userStates.valueAt(index);
+ if (enabled) {
+ userState.addHosts(domains);
+ } else {
+ userState.removeHosts(domains);
+ }
+ }
+ } else {
+ DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+ if (enabled) {
+ userState.addHosts(domains);
+ } else {
+ userState.removeHosts(domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName) throws NameNotFoundException {
+ return getDomainVerificationUserSelection(packageName,
+ mConnection.getCallingUserId());
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
+ mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), userId);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ return null;
+ }
+
+ AndroidPackage pkg = mConnection.getPackageLocked(packageName);
+ if (pkg == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ ArrayMap<String, Boolean> hostToUserSelectionMap = new ArrayMap<>();
+
+ ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
+ int domainsSize = domains.size();
+ for (int index = 0; index < domainsSize; index++) {
+ hostToUserSelectionMap.put(domains.valueAt(index), false);
+ }
+
+ boolean openVerifiedLinks = false;
+ DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+ if (userState != null) {
+ openVerifiedLinks = !userState.isDisallowLinkHandling();
+ ArraySet<String> enabledHosts = userState.getEnabledHosts();
+ int hostsSize = enabledHosts.size();
+ for (int index = 0; index < hostsSize; index++) {
+ hostToUserSelectionMap.put(enabledHosts.valueAt(index), true);
+ }
+ }
+
+ return new DomainVerificationUserSelection(pkgState.getId(), packageName,
+ UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
+ }
+ }
+
+ @NonNull
+ @Override
+ public UUID generateNewId() {
+ // TODO(b/159952358): Domain set ID collisions
+ return UUID.randomUUID();
+ }
+
+ @Override
+ public void migrateState(@NonNull PackageSetting oldPkgSetting,
+ @NonNull PackageSetting newPkgSetting) {
+ String pkgName = newPkgSetting.name;
+ boolean sendBroadcast;
+
+ synchronized (mLock) {
+ UUID oldDomainSetId = oldPkgSetting.getDomainSetId();
+ UUID newDomainSetId = newPkgSetting.getDomainSetId();
+ DomainVerificationPkgState oldPkgState = mAttachedPkgStates.remove(oldDomainSetId);
+
+ AndroidPackage oldPkg = oldPkgSetting.getPkg();
+ AndroidPackage newPkg = newPkgSetting.getPkg();
+
+ ArrayMap<String, Integer> newStateMap = new ArrayMap<>();
+ SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>();
+
+ if (oldPkgState == null || oldPkg == null || newPkg == null) {
+ // Should be impossible, but to be safe, continue with a new blank state instead
+ Slog.wtf(TAG, "Invalid state nullability old state = " + oldPkgState
+ + ", old pkgSetting = " + oldPkgSetting
+ + ", new pkgSetting = " + newPkgSetting
+ + ", old pkg = " + oldPkg
+ + ", new pkg = " + newPkg, new Exception());
+
+ DomainVerificationPkgState newPkgState = new DomainVerificationPkgState(
+ pkgName, newDomainSetId, true, newStateMap, newUserStates);
+ mAttachedPkgStates.put(pkgName, newDomainSetId, newPkgState);
+ return;
+ }
+
+ ArrayMap<String, Integer> oldStateMap = oldPkgState.getStateMap();
+ ArraySet<String> newAutoVerifyDomains = mCollector.collectAutoVerifyDomains(newPkg);
+ int newDomainsSize = newAutoVerifyDomains.size();
+
+ for (int newDomainsIndex = 0; newDomainsIndex < newDomainsSize; newDomainsIndex++) {
+ String domain = newAutoVerifyDomains.valueAt(newDomainsIndex);
+ Integer oldStateInteger = oldStateMap.get(domain);
+ if (oldStateInteger != null) {
+ int oldState = oldStateInteger;
+ switch (oldState) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_MIGRATED:
+ newStateMap.put(domain, oldState);
+ break;
+ default:
+ // In all other cases, the state code is left unset
+ // (STATE_NO_RESPONSE) to signal to the verification agent that any
+ // existing error has been cleared and the domain should be
+ // re-attempted. This makes update of a package a signal to
+ // re-verify.
+ break;
+ }
+ }
+ }
+
+ SparseArray<DomainVerificationUserState> oldUserStates =
+ oldPkgState.getUserSelectionStates();
+ int oldUserStatesSize = oldUserStates.size();
+ if (oldUserStatesSize > 0) {
+ ArraySet<String> newWebDomains = mCollector.collectAutoVerifyDomains(newPkg);
+ for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
+ oldUserStatesIndex++) {
+ int userId = oldUserStates.keyAt(oldUserStatesIndex);
+ DomainVerificationUserState oldUserState = oldUserStates.valueAt(
+ oldUserStatesIndex);
+ ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts();
+ ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
+ newEnabledHosts.retainAll(newWebDomains);
+ DomainVerificationUserState newUserState = new DomainVerificationUserState(
+ userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
+ newUserStates.put(userId, newUserState);
+ }
+ }
+
+ boolean hasAutoVerifyDomains = newDomainsSize > 0;
+ boolean needsBroadcast =
+ applyImmutableState(pkgName, newStateMap, newAutoVerifyDomains);
+
+ sendBroadcast = hasAutoVerifyDomains && needsBroadcast;
+
+ mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
+ pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates));
+ }
+
+ if (sendBroadcast) {
+ sendBroadcastForPackage(pkgName);
+ }
+ }
+
+ // TODO(b/159952358): Handle valid domainSetIds for PackageSettings with no AndroidPackage
+ @Override
+ public void addPackage(@NonNull PackageSetting newPkgSetting) {
+ // TODO(b/159952358): Optimize packages without any domains. Those wouldn't have to be in
+ // the state map, but it would require handling the "migration" case where an app either
+ // gains or loses all domains.
+
+ UUID domainSetId = newPkgSetting.getDomainSetId();
+ String pkgName = newPkgSetting.name;
+
+ boolean sendBroadcast = true;
+
+ DomainVerificationPkgState pkgState;
+ pkgState = mSettings.getPendingState(pkgName);
+ if (pkgState != null) {
+ // Don't send when attaching from pending read, which is usually boot scan. Re-send on
+ // boot is handled in a separate method once all packages are added.
+ sendBroadcast = false;
+ } else {
+ pkgState = mSettings.getRestoredState(pkgName);
+ }
+
+ AndroidPackage pkg = newPkgSetting.getPkg();
+ ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+ boolean hasAutoVerifyDomains = !domains.isEmpty();
+ boolean isPendingOrRestored = pkgState != null;
+ if (isPendingOrRestored) {
+ pkgState.setId(domainSetId);
+ } else {
+ pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains);
+ }
+
+ boolean needsBroadcast = applyImmutableState(pkgState, domains);
+ if (needsBroadcast && !isPendingOrRestored) {
+ // TODO(b/159952358): Test this behavior
+ // Attempt to preserve user experience by automatically verifying all domains from
+ // legacy state if they were previously approved, or by automatically enabling all
+ // hosts through user selection if legacy state indicates a user previously made the
+ // choice in settings to allow supported links. The domain verification agent should
+ // re-verify these links (set to STATE_MIGRATED) at the next possible opportunity,
+ // and disable them if appropriate.
+ ArraySet<String> webDomains = null;
+
+ SparseIntArray legacyUserStates = mLegacySettings.getUserStates(pkgName);
+ int userStateSize = legacyUserStates == null ? 0 : legacyUserStates.size();
+ for (int index = 0; index < userStateSize; index++) {
+ int userId = legacyUserStates.keyAt(index);
+ int legacyStatus = legacyUserStates.valueAt(index);
+ if (legacyStatus
+ == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+ if (webDomains == null) {
+ webDomains = mCollector.collectAllWebDomains(pkg);
+ }
+
+ pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains);
+ }
+ }
+
+ IntentFilterVerificationInfo legacyInfo = mLegacySettings.remove(pkgName);
+ if (legacyInfo != null
+ && legacyInfo.getStatus()
+ == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int domainsSize = domains.size();
+ for (int index = 0; index < domainsSize; index++) {
+ stateMap.put(domains.valueAt(index), DomainVerificationState.STATE_MIGRATED);
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ mAttachedPkgStates.put(pkgName, domainSetId, pkgState);
+ }
+
+ if (sendBroadcast && hasAutoVerifyDomains) {
+ sendBroadcastForPackage(pkgName);
+ }
+ }
+
+ private boolean applyImmutableState(@NonNull DomainVerificationPkgState pkgState,
+ @NonNull ArraySet<String> autoVerifyDomains) {
+ return applyImmutableState(pkgState.getPackageName(), pkgState.getStateMap(),
+ autoVerifyDomains);
+ }
+
+ /**
+ * Applies any immutable state as the final step when adding or migrating state. Currently only
+ * applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a package.
+ *
+ * @return whether or not a broadcast is necessary for this package
+ */
+ private boolean applyImmutableState(@NonNull String packageName,
+ @NonNull ArrayMap<String, Integer> stateMap,
+ @NonNull ArraySet<String> autoVerifyDomains) {
+ if (mSystemConfig.getLinkedApps().contains(packageName)) {
+ int domainsSize = autoVerifyDomains.size();
+ for (int index = 0; index < domainsSize; index++) {
+ stateMap.put(autoVerifyDomains.valueAt(index),
+ DomainVerificationState.STATE_SYS_CONFIG);
+ }
+ return false;
+ } else {
+ int size = stateMap.size();
+ for (int index = size - 1; index >= 0; index--) {
+ Integer state = stateMap.valueAt(index);
+ // If no longer marked in SysConfig, demote any previous SysConfig state
+ if (state == DomainVerificationState.STATE_SYS_CONFIG) {
+ stateMap.removeAt(index);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ @Override
+ public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
+ synchronized (mLock) {
+ mSettings.writeSettings(serializer, mAttachedPkgStates);
+ }
+
+ mLegacySettings.writeSettings(serializer);
+ }
+
+ @Override
+ public void readSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ synchronized (mLock) {
+ mSettings.readSettings(parser, mAttachedPkgStates);
+ }
+ }
+
+ @Override
+ public void readLegacySettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ mLegacySettings.readSettings(parser);
+ }
+
+ @Override
+ public void restoreSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ synchronized (mLock) {
+ mSettings.restoreSettings(parser, mAttachedPkgStates);
+ }
+ }
+
+ @Override
+ public void addLegacySetting(@NonNull String packageName,
+ @NonNull IntentFilterVerificationInfo info) {
+ mLegacySettings.add(packageName, info);
+ }
+
+ @Override
+ public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) {
+ mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid());
+ mLegacySettings.add(packageName, userId, state);
+ }
+
+ @Override
+ public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) {
+ 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);
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void clearUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ int attachedSize = mAttachedPkgStates.size();
+ for (int index = 0; index < attachedSize; index++) {
+ mAttachedPkgStates.valueAt(index).removeUser(userId);
+ }
+
+ mSettings.removeUser(userId);
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ return mProxy.runMessage(messageCode, object);
+ }
+
+ @Override
+ public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId) throws NameNotFoundException {
+ synchronized (mLock) {
+ mDebug.printState(writer, packageName, userId, mConnection, mAttachedPkgStates);
+ }
+ }
+
+ @NonNull
+ @Override
+ public DomainVerificationShell getShell() {
+ return mShell;
+ }
+
+ @NonNull
+ @Override
+ public DomainVerificationCollector getCollector() {
+ return mCollector;
+ }
+
+ private void sendBroadcastForPackage(@NonNull String packageName) {
+ mProxy.sendBroadcastForPackages(Collections.singleton(packageName));
+ }
+
+ private boolean hasRealVerifier() {
+ return !(mProxy instanceof DomainVerificationProxyUnavailable);
+ }
+
+ /**
+ * 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.
+ */
+ @GuardedBy("mLock")
+ private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean forAutoVerify)
+ throws InvalidDomainSetException, NameNotFoundException {
+ if (domainSetId == null) {
+ throw new InvalidDomainSetException(null, null,
+ InvalidDomainSetException.REASON_ID_NULL);
+ }
+
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId);
+ if (pkgState == null) {
+ throw new InvalidDomainSetException(domainSetId, null,
+ InvalidDomainSetException.REASON_ID_INVALID);
+ }
+
+ String pkgName = pkgState.getPackageName();
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
+ }
+
+ if (CollectionUtils.isEmpty(domains)) {
+ throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
+ InvalidDomainSetException.REASON_SET_NULL_OR_EMPTY);
+ }
+ AndroidPackage pkg = pkgSetting.getPkg();
+ ArraySet<String> declaredDomains = forAutoVerify
+ ? mCollector.collectAutoVerifyDomains(pkg)
+ : mCollector.collectAllWebDomains(pkg);
+
+ if (domains.retainAll(declaredDomains)) {
+ throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
+ InvalidDomainSetException.REASON_UNKNOWN_DOMAIN);
+ }
+
+ return pkgState;
+ }
+
+ @Override
+ public void verifyPackages(@Nullable List<String> packageNames, boolean reVerify) {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+ Set<String> packagesToBroadcast = new ArraySet<>();
+
+ if (packageNames == null) {
+ synchronized (mLock) {
+ int pkgStatesSize = mAttachedPkgStates.size();
+ for (int pkgStateIndex = 0; pkgStateIndex < pkgStatesSize; pkgStateIndex++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
+ addIfShouldBroadcastLocked(packagesToBroadcast, pkgState, reVerify);
+ }
+ }
+ } else {
+ synchronized (mLock) {
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = packageNames.get(index);
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState != null) {
+ addIfShouldBroadcastLocked(packagesToBroadcast, pkgState, reVerify);
+ }
+ }
+ }
+ }
+
+ if (!packagesToBroadcast.isEmpty()) {
+ mProxy.sendBroadcastForPackages(packagesToBroadcast);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void addIfShouldBroadcastLocked(@NonNull Collection<String> packageNames,
+ @NonNull DomainVerificationPkgState pkgState, boolean reVerify) {
+ if ((reVerify && pkgState.isHasAutoVerifyDomains()) || shouldReBroadcastPackage(pkgState)) {
+ packageNames.add(pkgState.getPackageName());
+ }
+ }
+
+ /**
+ * Determine whether or not a broadcast should be sent at boot for the given {@param pkgState}.
+ * Sends only if the only states recorded are default as decided by {@link
+ * DomainVerificationManager#isStateDefault(int)}.
+ *
+ * If any other state is set, it's assumed that the domain verification agent is aware of the
+ * package and has already scheduled future verification requests.
+ */
+ private boolean shouldReBroadcastPackage(DomainVerificationPkgState pkgState) {
+ if (!pkgState.isHasAutoVerifyDomains()) {
+ return false;
+ }
+
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int statesSize = stateMap.size();
+ for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
+ Integer state = stateMap.valueAt(stateIndex);
+ if (!DomainVerificationManager.isStateDefault(state)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void clearDomainVerificationState(@Nullable List<String> packageNames) {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+ synchronized (mLock) {
+ if (packageNames == null) {
+ int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ String pkgName = pkgState.getPackageName();
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+ resetDomainState(pkgState, pkgSetting.getPkg());
+ }
+ } else {
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String pkgName = packageNames.get(index);
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+ resetDomainState(pkgState, pkgSetting.getPkg());
+ }
+ }
+ }
+ }
+
+ /**
+ * Reset states that are mutable by the domain verification agent.
+ */
+ private void resetDomainState(@NonNull DomainVerificationPkgState pkgState,
+ @NonNull AndroidPackage pkg) {
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int size = stateMap.size();
+ for (int index = size - 1; index >= 0; index--) {
+ Integer state = stateMap.valueAt(index);
+ boolean reset;
+ switch (state) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_RESTORED:
+ reset = true;
+ break;
+ default:
+ reset = state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ break;
+ }
+
+ if (reset) {
+ stateMap.removeAt(index);
+ }
+ }
+
+ applyImmutableState(pkgState, mCollector.collectAutoVerifyDomains(pkg));
+ }
+
+ @Override
+ public void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId) {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+ synchronized (mLock) {
+ if (packageNames == null) {
+ int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ if (userId == UserHandle.USER_ALL) {
+ pkgState.removeAllUsers();
+ } else {
+ pkgState.removeUser(userId);
+ }
+ }
+ } else {
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String pkgName = packageNames.get(index);
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
+ if (userId == UserHandle.USER_ALL) {
+ pkgState.removeAllUsers();
+ } else {
+ pkgState.removeUser(userId);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+ @UserIdInt int userId) {
+ String packageName = pkgSetting.name;
+ if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "not valid intent");
+ }
+ return false;
+ }
+
+ String host = intent.getData().getHost();
+ final AndroidPackage pkg = pkgSetting.getPkg();
+
+ // Should never be null, but if it is, skip this and assume that v2 is enabled
+ if (pkg != null) {
+ // To allow an instant app to immediately open domains after being installed by the
+ // user, auto approve them for any declared autoVerify domains.
+ if (pkgSetting.getInstantApp(userId)
+ && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
+ return true;
+ }
+
+ if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
+ int legacyState = mLegacySettings.getUserState(packageName, userId);
+ switch (legacyState) {
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ // If nothing specifically set, assume v2 rules
+ break;
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+ // With v2 split into 2 lists, always and undefined, the concept of whether
+ // or not to ask is irrelevant. Assume the user wants this application to
+ // open the domain.
+ return true;
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+ // Never has the same semantics are before
+ return false;
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "pkgState unavailable");
+ }
+ return false;
+ }
+
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+
+ // Only allow autoVerify approval if the user hasn't disabled it
+ if (userState == null || !userState.isDisallowLinkHandling()) {
+ // Check if the exact host matches
+ Integer state = stateMap.get(host);
+ if (state != null && DomainVerificationManager.isStateVerified(state)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true, "host verified exactly");
+ }
+ return true;
+ }
+
+ // Otherwise see if the host matches a verified domain by wildcard
+ int stateMapSize = stateMap.size();
+ for (int index = 0; index < stateMapSize; index++) {
+ if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+ continue;
+ }
+
+ String domain = stateMap.keyAt(index);
+ if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true,
+ "host verified by wildcard");
+ }
+ return true;
+ }
+ }
+ }
+
+ // Check user state if available
+ if (userState == null) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "userState unavailable");
+ }
+ return false;
+ }
+
+ // See if the user has approved the exact host
+ ArraySet<String> enabledHosts = userState.getEnabledHosts();
+ if (enabledHosts.contains(host)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true,
+ "host enabled by user exactly");
+ }
+ return true;
+ }
+
+ // See if the host matches a user selection by wildcard
+ int enabledHostsSize = enabledHosts.size();
+ for (int index = 0; index < enabledHostsSize; index++) {
+ String domain = enabledHosts.valueAt(index);
+ if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true,
+ "host enabled by user through wildcard");
+ }
+ return true;
+ }
+ }
+
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "not approved");
+ }
+ return false;
+ }
+ }
+
+ private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
+ @UserIdInt int userId, boolean approved, @NonNull String reason) {
+ String approvalString = approved ? "approved" : "denied";
+ Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
+ + " for user " + userId + ": " + reason);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
new file mode 100644
index 0000000..073967e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+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 org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class DomainVerificationSettings {
+
+ /**
+ * States read from disk that have yet to attach to a package, but are expected to, generally in
+ * the context of scanning packages already on disk. This is expected to be empty once the boot
+ * package scan completes.
+ **/
+ @GuardedBy("mLock")
+ @NonNull
+ private final ArrayMap<String, DomainVerificationPkgState> mPendingPkgStates = new ArrayMap<>();
+
+ /**
+ * States from restore that have yet to attach to a package. These are special in that their IDs
+ * are dropped when the package is installed/otherwise becomes available, because the ID will
+ * not match if the data is restored from a different device install.
+ * <p>
+ * If multiple restore calls come in and they overlap, the latest entry added for a package name
+ * will be taken, dropping any previous versions.
+ **/
+ @GuardedBy("mLock")
+ @NonNull
+ private final ArrayMap<String, DomainVerificationPkgState> mRestoredPkgStates =
+ new ArrayMap<>();
+
+ /**
+ * Lock for all state reads/writes.
+ */
+ private final Object mLock = new Object();
+
+
+ public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ throws IOException {
+ synchronized (mLock) {
+ DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
+ mPendingPkgStates, mRestoredPkgStates);
+ }
+ }
+
+ /**
+ * Parses a previously stored set of states and merges them with {@param liveState}, directly
+ * mutating the values. This is intended for reading settings written by {@link
+ * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap)} on the same device setup.
+ */
+ public void readSettings(@NonNull TypedXmlPullParser parser,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ throws IOException, XmlPullParserException {
+ DomainVerificationPersistence.ReadResult result =
+ DomainVerificationPersistence.readFromXml(parser);
+ ArrayMap<String, DomainVerificationPkgState> active = result.active;
+ ArrayMap<String, DomainVerificationPkgState> restored = result.restored;
+
+ synchronized (mLock) {
+ int activeSize = active.size();
+ for (int activeIndex = 0; activeIndex < activeSize; activeIndex++) {
+ DomainVerificationPkgState pkgState = active.valueAt(activeIndex);
+ String pkgName = pkgState.getPackageName();
+ DomainVerificationPkgState existingState = liveState.get(pkgName);
+ if (existingState != null) {
+ // This branch should never be possible. Settings should be read from disk
+ // before any states are attached. But just in case, handle it.
+ if (!existingState.getId().equals(pkgState.getId())) {
+ mergePkgState(existingState, pkgState);
+ }
+ } else {
+ mPendingPkgStates.put(pkgName, pkgState);
+ }
+ }
+
+ int restoredSize = restored.size();
+ for (int restoredIndex = 0; restoredIndex < restoredSize; restoredIndex++) {
+ DomainVerificationPkgState pkgState = restored.valueAt(restoredIndex);
+ mRestoredPkgStates.put(pkgState.getPackageName(), pkgState);
+ }
+ }
+ }
+
+ /**
+ * Parses a previously stored set of states and merges them with {@param liveState}, directly
+ * mutating the values. This is intended for restoration across device setups.
+ */
+ public void restoreSettings(@NonNull TypedXmlPullParser parser,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ throws IOException, XmlPullParserException {
+ // TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on
+ // a new device.
+
+ DomainVerificationPersistence.ReadResult result =
+ DomainVerificationPersistence.readFromXml(parser);
+
+ // When restoring settings, both active and previously restored are merged, since they
+ // should both go into the newly restored data. Active is added on top of restored just
+ // in case a duplicate is found. Active should be preferred.
+ ArrayMap<String, DomainVerificationPkgState> stateList = result.restored;
+ stateList.putAll(result.active);
+
+ synchronized (mLock) {
+ for (int stateIndex = 0; stateIndex < stateList.size(); stateIndex++) {
+ DomainVerificationPkgState newState = stateList.valueAt(stateIndex);
+ String pkgName = newState.getPackageName();
+ DomainVerificationPkgState existingState = liveState.get(pkgName);
+ if (existingState == null) {
+ existingState = mPendingPkgStates.get(pkgName);
+ }
+ if (existingState == null) {
+ existingState = mRestoredPkgStates.get(pkgName);
+ }
+
+ if (existingState != null) {
+ mergePkgState(existingState, newState);
+ } else {
+ // If there's no existing state, that means the new state has to be transformed
+ // in preparation for attaching to brand new package that may eventually be
+ // installed. This means coercing STATE_SUCCESS and STATE_RESTORED to
+ // STATE_RESTORED and dropping everything else, the same logic that
+ // mergePkgState runs, without the merge part.
+ ArrayMap<String, Integer> stateMap = newState.getStateMap();
+ int size = stateMap.size();
+ for (int index = 0; index < size; index++) {
+ Integer stateInteger = stateMap.valueAt(index);
+ if (stateInteger != null) {
+ int state = stateInteger;
+ if (state == DomainVerificationState.STATE_SUCCESS
+ || state == DomainVerificationState.STATE_RESTORED) {
+ stateMap.setValueAt(index, state);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Merges a newly restored state with existing state. This should only be called for restore,
+ * when the IDs aren't required to match.
+ * <p>
+ * If the existing state for a domain is
+ * {@link DomainVerificationState#STATE_NO_RESPONSE}, then it will be overridden with
+ * {@link DomainVerificationState#STATE_RESTORED} if the restored state is
+ * {@link DomainVerificationState#STATE_SUCCESS} or
+ * {@link DomainVerificationState#STATE_RESTORED}.
+ * <p>
+ * Otherwise the existing state is preserved, assuming any system rules, success state, or
+ * specific error codes are fresher than the restored state. Essentially state is only restored
+ * to grant additional verifications to an app.
+ * <p>
+ * For user selection state, presence in either state will be considered an enabled host. NOTE:
+ * only {@link UserHandle#USER_SYSTEM} is merged. There is no restore path in place for
+ * multiple users.
+ * <p>
+ * TODO(b/170746586): Figure out the restore path for multiple users
+ * <p>
+ * This will mutate {@param oldState} to contain the merged state.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static void mergePkgState(@NonNull DomainVerificationPkgState oldState,
+ @NonNull DomainVerificationPkgState newState) {
+ ArrayMap<String, Integer> oldStateMap = oldState.getStateMap();
+ ArrayMap<String, Integer> newStateMap = newState.getStateMap();
+ int size = newStateMap.size();
+ for (int index = 0; index < size; index++) {
+ String domain = newStateMap.keyAt(index);
+ Integer newStateCode = newStateMap.valueAt(index);
+ Integer oldStateCodeInteger = oldStateMap.get(domain);
+ if (oldStateCodeInteger == null) {
+ // Cannot add domains to an app
+ continue;
+ }
+
+ int oldStateCode = oldStateCodeInteger;
+ if (oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) {
+ if (newStateCode == DomainVerificationState.STATE_SUCCESS
+ || newStateCode == DomainVerificationState.STATE_RESTORED) {
+ oldStateMap.put(domain, DomainVerificationState.STATE_RESTORED);
+ }
+ }
+ }
+
+ SparseArray<DomainVerificationUserState> oldSelectionStates =
+ oldState.getUserSelectionStates();
+
+ SparseArray<DomainVerificationUserState> newSelectionStates =
+ newState.getUserSelectionStates();
+
+ DomainVerificationUserState newUserState = newSelectionStates.get(UserHandle.USER_SYSTEM);
+ if (newUserState != null) {
+ ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
+ DomainVerificationUserState oldUserState =
+ oldSelectionStates.get(UserHandle.USER_SYSTEM);
+
+ boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
+ if (oldUserState == null) {
+ oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
+ newEnabledHosts, disallowLinkHandling);
+ oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
+ } else {
+ oldUserState.addHosts(newEnabledHosts)
+ .setDisallowLinkHandling(disallowLinkHandling);
+ }
+ }
+ }
+
+ public void removeUser(@UserIdInt int userId) {
+ int pendingSize = mPendingPkgStates.size();
+ for (int index = 0; index < pendingSize; index++) {
+ mPendingPkgStates.valueAt(index).removeUser(userId);
+ }
+
+ // TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
+ // on a new device
+ int restoredSize = mRestoredPkgStates.size();
+ for (int index = 0; index < restoredSize; index++) {
+ mRestoredPkgStates.valueAt(index).removeUser(userId);
+ }
+ }
+
+ @Nullable
+ public DomainVerificationPkgState getPendingState(@NonNull String pkgName) {
+ synchronized (mLock) {
+ return mPendingPkgStates.get(pkgName);
+ }
+ }
+
+ @Nullable
+ public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) {
+ synchronized (mLock) {
+ return mRestoredPkgStates.get(pkgName);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
new file mode 100644
index 0000000..7f9e75a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class DomainVerificationShell {
+
+ @NonNull
+ private final Callback mCallback;
+
+ public DomainVerificationShell(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
+ public void printHelp(@NonNull PrintWriter pw) {
+ pw.println(" get-app-links [--user <USER_ID>] [<PACKAGE>]");
+ pw.println(" Prints the domain verification state for the given package, or for all");
+ pw.println(" packages if none is specified.");
+ pw.println(" --user <USER_ID>: include user selections (includes all domains, not");
+ pw.println(" just autoVerify ones)");
+ pw.println(" reset-app-links [--user <USER_ID>] [<PACKAGE>]");
+ pw.println(" Resets domain verification state for the given package, or for all");
+ pw.println(" packages if none is specified.");
+ pw.println(" --user <USER_ID>: clear user selection state instead; note this means");
+ pw.println(" domain verification state will NOT be cleared");
+ pw.println(" <PACKAGE>: the package to reset, or \"all\" to reset all packages");
+ pw.println(" verify-app-links [--re-verify] [<PACKAGE>]");
+ pw.println(" Broadcasts a verification request for the given package, or for all");
+ pw.println(" packages if none is specified. Only sends if the package has previously");
+ pw.println(" not recorded a response.");
+ pw.println(" --re-verify: send even if the package has recorded a response");
+ pw.println(" set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>...");
+ pw.println(" Manually set the state of a domain for a package. The domain must be");
+ pw.println(" declared by the package as autoVerify for this to work. This command");
+ pw.println(" will not report a failure for domains that could not be applied.");
+ pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+ pw.println(" <STATE>: the code to set the domains to, valid values are:");
+ pw.println(" STATE_NO_RESPONSE (0): reset as if no response was ever recorded.");
+ pw.println(" STATE_SUCCESS (1): treat domain as successfully verified by domain.");
+ pw.println(" verification agent. Note that the domain verification agent can");
+ pw.println(" override this.");
+ pw.println(" STATE_APPROVED (2): treat domain as always approved, preventing the");
+ pw.println(" domain verification agent from changing it.");
+ pw.println(" STATE_DENIED (3): treat domain as always denied, preveting the domain");
+ pw.println(" verification agent from changing it.");
+ pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
+ pw.println(" change every domain.");
+ pw.println(" set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>]");
+ pw.println(" <ENABLED> <DOMAINS>...");
+ pw.println(" Manually set the state of a host user selection for a package. The domain");
+ pw.println(" must be declared by the package for this to work. This command will not");
+ pw.println(" report a failure for domains that could not be applied.");
+ pw.println(" --user <USER_ID>: the user to change selections for");
+ pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+ pw.println(" <ENABLED>: whether or not to approve the domain");
+ pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
+ pw.println(" change every domain.");
+ pw.println(" set-app-links-allowed --user <USER_ID> [--package <PACKAGE>] <ALLOWED>");
+ pw.println(" <ENABLED> <DOMAINS>...");
+ pw.println(" Toggle the auto verified link handling setting for a package.");
+ pw.println(" --user <USER_ID>: the user to change selections for");
+ pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+ pw.println(" packages will be reset if no one package is specified.");
+ pw.println(" <ALLOWED>: true to allow the package to open auto verified links, false");
+ pw.println(" to disable");
+ }
+
+ /**
+ * Run a shell/debugging command.
+ *
+ * @return null if the command is unhandled, true if the command succeeded, false if it failed
+ */
+ public Boolean runCommand(@NonNull BasicShellCommandHandler commandHandler,
+ @NonNull String command) {
+ switch (command) {
+ case "get-app-links":
+ return runGetAppLinks(commandHandler);
+ case "reset-app-links":
+ return runResetAppLinks(commandHandler);
+ case "verify-app-links":
+ return runVerifyAppLinks(commandHandler);
+ case "set-app-links":
+ return runSetAppLinks(commandHandler);
+ case "set-app-links-user-selection":
+ return runSetAppLinksUserSelection(commandHandler);
+ case "set-app-links-allowed":
+ return runSetAppLinksAllowed(commandHandler);
+ }
+
+ return null;
+ }
+
+
+ // pm set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>...
+ private boolean runSetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ String packageName = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--package")) {
+ packageName = commandHandler.getNextArgRequired();
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ if (TextUtils.isEmpty(packageName)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (packageName.equalsIgnoreCase("all")) {
+ packageName = null;
+ }
+
+ String state = commandHandler.getNextArgRequired();
+ int stateInt;
+ switch (state) {
+ case "STATE_NO_RESPONSE":
+ case "0":
+ stateInt = DomainVerificationState.STATE_NO_RESPONSE;
+ break;
+ case "STATE_SUCCESS":
+ case "1":
+ stateInt = DomainVerificationState.STATE_SUCCESS;
+ break;
+ case "STATE_APPROVED":
+ case "2":
+ stateInt = DomainVerificationState.STATE_APPROVED;
+ break;
+ case "STATE_DENIED":
+ case "3":
+ stateInt = DomainVerificationState.STATE_DENIED;
+ break;
+ default:
+ commandHandler.getErrPrintWriter().println("Invalid state option: " + state);
+ return false;
+ }
+
+ ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
+ if (domains.isEmpty()) {
+ commandHandler.getErrPrintWriter().println("No domains specified");
+ return false;
+ }
+
+ if (domains.size() == 1 && domains.contains("all")) {
+ domains = null;
+ }
+
+ try {
+ mCallback.setDomainVerificationStatusInternal(packageName, stateInt,
+ domains);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
+ // pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>...
+ private boolean runSetAppLinksUserSelection(@NonNull BasicShellCommandHandler commandHandler) {
+ Integer userId = null;
+ String packageName = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ switch (option) {
+ case "--user":
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ break;
+ case "--package":
+ packageName = commandHandler.getNextArgRequired();
+ break;
+ default:
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ if (TextUtils.isEmpty(packageName)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (packageName.equalsIgnoreCase("all")) {
+ packageName = null;
+ }
+
+ if (userId == null) {
+ commandHandler.getErrPrintWriter().println("Error: User ID not specified");
+ return false;
+ }
+
+ userId = translateUserId(userId, "runSetAppLinksUserSelection");
+
+ String enabledString = commandHandler.getNextArgRequired();
+
+ // Manually ensure that "true" and "false" are the only options, to ensure a domain isn't
+ // accidentally parsed as a boolean
+ boolean enabled;
+ switch (enabledString) {
+ case "true":
+ enabled = true;
+ break;
+ case "false":
+ enabled = false;
+ break;
+ default:
+ commandHandler.getErrPrintWriter().println(
+ "Invalid enabled param: " + enabledString);
+ return false;
+ }
+
+ ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
+ if (domains.isEmpty()) {
+ commandHandler.getErrPrintWriter().println("No domains specified");
+ return false;
+ }
+
+ try {
+ mCallback.setDomainVerificationUserSelectionInternal(userId,
+ packageName, enabled, domains);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
+ // pm get-app-links [--user <USER_ID>] [<PACKAGE>]
+ private boolean runGetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ Integer userId = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--user")) {
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ userId = userId == null ? null : translateUserId(userId, "runGetAppLinks");
+
+ String packageName = commandHandler.getNextArg();
+
+ try (IndentingPrintWriter writer = new IndentingPrintWriter(
+ commandHandler.getOutPrintWriter(), /* singleIndent */ " ", /* wrapLength */
+ 120)) {
+ writer.increaseIndent();
+ try {
+ mCallback.printState(writer, packageName, userId);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println(
+ "Error: package " + packageName + " unavailable");
+ return false;
+ }
+ writer.decreaseIndent();
+ return true;
+ }
+ }
+
+ // pm reset-app-links [--user USER_ID] [<PACKAGE>]
+ private boolean runResetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ Integer userId = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--user")) {
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ userId = userId == null ? null : translateUserId(userId, "runResetAppLinks");
+
+ List<String> packageNames;
+ String pkgNameArg = commandHandler.peekNextArg();
+ if (TextUtils.isEmpty(pkgNameArg)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (pkgNameArg.equalsIgnoreCase("all")) {
+ packageNames = null;
+ } else {
+ packageNames = Arrays.asList(commandHandler.peekRemainingArgs());
+ }
+
+ if (userId != null) {
+ mCallback.clearUserSelections(packageNames, userId);
+ } else {
+ mCallback.clearDomainVerificationState(packageNames);
+ }
+
+ return true;
+ }
+
+ // pm verify-app-links [--re-verify] [<PACKAGE>]
+ private boolean runVerifyAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ boolean reVerify = false;
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--re-verify")) {
+ reVerify = true;
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ List<String> packageNames = null;
+ String pkgNameArg = commandHandler.getNextArg();
+ if (!TextUtils.isEmpty(pkgNameArg)) {
+ packageNames = Collections.singletonList(pkgNameArg);
+ }
+
+ mCallback.verifyPackages(packageNames, reVerify);
+
+ return true;
+ }
+
+ // pm set-app-links-allowed [--package <PACKAGE>] [--user <USER_ID>] <ALLOWED>
+ private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) {
+ String packageName = null;
+ Integer userId = null;
+ Boolean allowed = null;
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--package")) {
+ packageName = commandHandler.getNextArgRequired();
+ } if (option.equals("--user")) {
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ } else if (allowed == null) {
+ allowed = Boolean.valueOf(option);
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option);
+ return false;
+ }
+ }
+
+ if (TextUtils.isEmpty(packageName)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (packageName.equalsIgnoreCase("all")) {
+ packageName = null;
+ }
+
+ if (userId == null) {
+ commandHandler.getErrPrintWriter().println("Error: user ID not specified");
+ return false;
+ }
+
+ if (allowed == null) {
+ commandHandler.getErrPrintWriter().println("Error: allowed setting not specified");
+ return false;
+ }
+
+ userId = translateUserId(userId, "runSetAppLinksAllowed");
+
+ try {
+ mCallback.setDomainVerificationLinkHandlingAllowedInternal(packageName, allowed,
+ userId);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+ return false;
+ }
+
+ return true;
+ }
+
+ private ArrayList<String> getRemainingArgs(@NonNull BasicShellCommandHandler commandHandler) {
+ ArrayList<String> args = new ArrayList<>();
+ String arg;
+ while ((arg = commandHandler.getNextArg()) != null) {
+ args.add(arg);
+ }
+ return args;
+ }
+
+ private int translateUserId(@UserIdInt int userId, @NonNull String logContext) {
+ return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, true, true, logContext, "pm command");
+ }
+
+ /**
+ * Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are
+ * even more internal, and so that testing is easier.
+ */
+ public interface Callback {
+
+ /**
+ * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+ * the state for a domain.
+ *
+ * @param packageName the package whose state to change, or all packages if none is
+ * specified
+ * @param state the new state code, valid values are
+ * {@link DomainVerificationState#STATE_NO_RESPONSE},
+ * {@link DomainVerificationState#STATE_SUCCESS}, {@link
+ * DomainVerificationState#STATE_APPROVED}, and {@link
+ * DomainVerificationState#STATE_DENIED}
+ * @param domains the set of domains to change, or null to change all of them
+ */
+ void setDomainVerificationStatusInternal(@Nullable String packageName, int state,
+ @Nullable ArraySet<String> domains) throws PackageManager.NameNotFoundException;
+
+ /**
+ * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+ * the state for a domain.
+ *
+ * @param packageName the package whose state to change, or all packages if non is
+ * specified
+ * @param enabled whether the domain is now approved by the user
+ * @param domains the set of domains to change
+ */
+ void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
+ @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * @see DomainVerificationManager#getDomainVerificationUserSelection(String)
+ */
+ @Nullable
+ DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+ * the setting for a package.
+ *
+ * @param packageName the package whose state to change, or all packages if non is
+ * specified
+ * @param allowed whether the package is allowed to automatically open links through
+ * domain verification
+ */
+ void setDomainVerificationLinkHandlingAllowedInternal(@Nullable String packageName,
+ boolean allowed, @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Reset all the domain verification states for all domains for the given package names, or
+ * all package names if null is provided.
+ */
+ void clearDomainVerificationState(@Nullable List<String> packageNames);
+
+ /**
+ * Reset all the user selections for the given package names, or all package names if null
+ * is provided.
+ */
+ void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId);
+
+ /**
+ * Broadcast a verification request for the given package names, or all package names if
+ * null is provided. By default only re-broadcasts if a package has not recorded a
+ * response.
+ *
+ * @param reVerify send even if the package has previously recorded a response
+ */
+ void verifyPackages(@Nullable List<String> packageNames, boolean reVerify);
+
+ /**
+ * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer)
+ */
+ void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
new file mode 100644
index 0000000..474f822
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain;
+
+import android.annotation.CheckResult;
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+final class DomainVerificationUtils {
+
+ /**
+ * Consolidates package exception messages. A generic unavailable message is included since the
+ * caller doesn't bother to check why the package isn't available.
+ */
+ @CheckResult
+ static NameNotFoundException throwPackageUnavailable(@NonNull String packageName)
+ throws NameNotFoundException {
+ throw new NameNotFoundException("Package " + packageName + " unavailable");
+ }
+
+ static boolean isDomainVerificationIntent(Intent intent) {
+ return intent.isWebIntent()
+ && intent.hasCategory(Intent.CATEGORY_BROWSABLE)
+ && intent.hasCategory(Intent.CATEGORY_DEFAULT);
+ }
+
+ static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,
+ long changeId) {
+ //noinspection ConstantConditions
+ return Binder.withCleanCallingIdentity(
+ () -> platformCompat.isChangeEnabled(changeId, buildMockAppInfo(pkg)));
+ }
+
+ /**
+ * Passed to {@link PlatformCompat} because this can be invoked mid-install process or when
+ * {@link PackageManagerService#mLock} is being held, and {@link PlatformCompat} will not be
+ * able to query the pending {@link ApplicationInfo} from {@link PackageManager}.
+ * <p>
+ * TODO(b/177613575): Can a different API be used?
+ */
+ @NonNull
+ private static ApplicationInfo buildMockAppInfo(@NonNull AndroidPackage pkg) {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = pkg.getPackageName();
+ appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
+ return appInfo;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
new file mode 100644
index 0000000..c6c9791
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "PackageManagerServiceUnitTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.verify.domain"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
new file mode 100644
index 0000000..48099aa
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain.models;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * State for a single package for the domain verification APIs. Stores the state of each individual
+ * domain declared by the package, including its verification state and user selection state.
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class DomainVerificationPkgState {
+
+ @NonNull
+ private final String mPackageName;
+
+ @NonNull
+ private UUID mId;
+
+ /**
+ * Whether or not the package declares any autoVerify domains. This is separate from an empty
+ * check on the map itself, because an empty map means no response recorded, not necessarily no
+ * domains declared. When this is false, {@link #mStateMap} will be empty, but
+ * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to
+ * allow this package to open, which may or may not be marked autoVerify.
+ */
+ private final boolean mHasAutoVerifyDomains;
+
+ /**
+ * Map of domains to state integers. Only domains that are not set to the default value of
+ * {@link DomainVerificationState#STATE_NO_RESPONSE} are included.
+ *
+ * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+ * such as storing no state when the package is marked as a linked app in SystemConfig.
+ */
+ @NonNull
+ private final ArrayMap<String, Integer> mStateMap;
+
+ @NonNull
+ private final SparseArray<DomainVerificationUserState> mUserSelectionStates;
+
+ public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
+ boolean hasAutoVerifyDomains) {
+ this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0));
+ }
+
+ @Nullable
+ public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) {
+ return mUserSelectionStates.get(userId);
+ }
+
+ @Nullable
+ public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) {
+ DomainVerificationUserState userState = mUserSelectionStates.get(userId);
+ if (userState == null) {
+ userState = new DomainVerificationUserState(userId);
+ mUserSelectionStates.put(userId, userState);
+ }
+ return userState;
+ }
+
+ public void setId(@NonNull UUID id) {
+ mId = id;
+ }
+
+ public void removeUser(@UserIdInt int userId) {
+ mUserSelectionStates.remove(userId);
+ }
+
+ public void removeAllUsers() {
+ mUserSelectionStates.clear();
+ }
+
+ private int userSelectionStatesHashCode() {
+ return mUserSelectionStates.contentHashCode();
+ }
+
+ private boolean userSelectionStatesEquals(
+ @NonNull SparseArray<DomainVerificationUserState> other) {
+ return mUserSelectionStates.contentEquals(other);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationPkgState.
+ *
+ * @param stateMap
+ * Map of domains to state integers. Only domains that are not set to the default value of
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+ *
+ * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+ * such as storing no state when the package is marked as a linked app in SystemConfig.
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationPkgState(
+ @NonNull String packageName,
+ @NonNull UUID id,
+ boolean hasAutoVerifyDomains,
+ @NonNull ArrayMap<String,Integer> stateMap,
+ @NonNull SparseArray<DomainVerificationUserState> userSelectionStates) {
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mId = id;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mId);
+ this.mHasAutoVerifyDomains = hasAutoVerifyDomains;
+ this.mStateMap = stateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mStateMap);
+ this.mUserSelectionStates = userSelectionStates;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUserSelectionStates);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull UUID getId() {
+ return mId;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isHasAutoVerifyDomains() {
+ return mHasAutoVerifyDomains;
+ }
+
+ /**
+ * Map of domains to state integers. Only domains that are not set to the default value of
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+ *
+ * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+ * such as storing no state when the package is marked as a linked app in SystemConfig.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ArrayMap<String,Integer> getStateMap() {
+ return mStateMap;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull SparseArray<DomainVerificationUserState> getUserSelectionStates() {
+ return mUserSelectionStates;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationPkgState { " +
+ "packageName = " + mPackageName + ", " +
+ "id = " + mId + ", " +
+ "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
+ "stateMap = " + mStateMap + ", " +
+ "userSelectionStates = " + mUserSelectionStates +
+ " }";
+ }
+
+ @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(DomainVerificationPkgState other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationPkgState that = (DomainVerificationPkgState) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mId, that.mId)
+ && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
+ && Objects.equals(mStateMap, that.mStateMap)
+ && userSelectionStatesEquals(that.mUserSelectionStates);
+ }
+
+ @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(mId);
+ _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
+ _hash = 31 * _hash + Objects.hashCode(mStateMap);
+ _hash = 31 * _hash + userSelectionStatesHashCode();
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1608234185474L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState> mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userSelectionStatesHashCode()\nprivate boolean userSelectionStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java
new file mode 100644
index 0000000..88ccd83
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain.models;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * A feature specific implementation of a multi-key map, since lookups by both a {@link String}
+ * package name and {@link UUID} domain set ID should be supported.
+ *
+ * @param <ValueType> stored object type
+ */
+public class DomainVerificationStateMap<ValueType> {
+
+ private static final String TAG = "DomainVerificationStateMap";
+
+ @NonNull
+ private final ArrayMap<String, ValueType> mPackageNameMap = new ArrayMap<>();
+
+ @NonNull
+ private final ArrayMap<UUID, ValueType> mDomainSetIdMap = new ArrayMap<>();
+
+ public int size() {
+ return mPackageNameMap.size();
+ }
+
+ @NonNull
+ public ValueType valueAt(@IntRange(from = 0) int index) {
+ return mPackageNameMap.valueAt(index);
+ }
+
+ @Nullable
+ public ValueType get(@NonNull String packageName) {
+ return mPackageNameMap.get(packageName);
+ }
+
+ @Nullable
+ public ValueType get(@NonNull UUID domainSetId) {
+ return mDomainSetIdMap.get(domainSetId);
+ }
+
+ public void put(@NonNull String packageName, @NonNull UUID domainSetId,
+ @NonNull ValueType valueType) {
+ if (mPackageNameMap.containsKey(packageName)) {
+ remove(packageName);
+ }
+
+ mPackageNameMap.put(packageName, valueType);
+ mDomainSetIdMap.put(domainSetId, valueType);
+ }
+
+ @Nullable
+ public ValueType remove(@NonNull String packageName) {
+ ValueType valueRemoved = mPackageNameMap.remove(packageName);
+ if (valueRemoved != null) {
+ int index = mDomainSetIdMap.indexOfValue(valueRemoved);
+ if (index >= 0) {
+ mDomainSetIdMap.removeAt(index);
+ }
+ }
+ return valueRemoved;
+ }
+
+ @Nullable
+ public ValueType remove(@NonNull UUID domainSetId) {
+ ValueType valueRemoved = mDomainSetIdMap.remove(domainSetId);
+ if (valueRemoved != null) {
+ int index = mPackageNameMap.indexOfValue(valueRemoved);
+ if (index >= 0) {
+ mPackageNameMap.removeAt(index);
+ }
+ }
+ return valueRemoved;
+ }
+
+ @NonNull
+ public List<String> getPackageNames() {
+ return new ArrayList<>(mPackageNameMap.keySet());
+ }
+
+ /**
+ * Exposes the backing values collection of the one of the internal maps. Should only be used
+ * for test assertions.
+ */
+ @VisibleForTesting
+ public Collection<ValueType> values() {
+ return new ArrayList<>(mPackageNameMap.values());
+ }
+
+ @Override
+ public String toString() {
+ return "DomainVerificationStateMap{"
+ + "packageNameMap=" + mPackageNameMap
+ + ", domainSetIdMap=" + mDomainSetIdMap
+ + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
new file mode 100644
index 0000000..8e82608
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain.models;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.util.ArraySet;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Set;
+
+/**
+ * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
+ * that domain when a web URL Intent is sent ft.
+ */
+@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
+public class DomainVerificationUserState {
+
+ @UserIdInt
+ private final int mUserId;
+
+ /** List of domains which have been enabled by the user. **/
+ @NonNull
+ private final ArraySet<String> mEnabledHosts;
+
+ /** Whether to disallow this package from automatically opening links by auto verification. */
+ private boolean mDisallowLinkHandling;
+
+ public DomainVerificationUserState(@UserIdInt int userId) {
+ mUserId = userId;
+ mEnabledHosts = new ArraySet<>();
+ }
+
+ public DomainVerificationUserState addHosts(@NonNull ArraySet<String> newHosts) {
+ mEnabledHosts.addAll(newHosts);
+ return this;
+ }
+
+ public DomainVerificationUserState addHosts(@NonNull Set<String> newHosts) {
+ mEnabledHosts.addAll(newHosts);
+ return this;
+ }
+
+ public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) {
+ mEnabledHosts.removeAll(newHosts);
+ return this;
+ }
+
+ public DomainVerificationUserState removeHosts(@NonNull Set<String> newHosts) {
+ mEnabledHosts.removeAll(newHosts);
+ return this;
+ }
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationUserState.
+ *
+ * @param enabledHosts
+ * List of domains which have been enabled by the user. *
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationUserState(
+ @UserIdInt int userId,
+ @NonNull ArraySet<String> enabledHosts,
+ boolean disallowLinkHandling) {
+ this.mUserId = userId;
+ com.android.internal.util.AnnotationValidations.validate(
+ UserIdInt.class, null, mUserId);
+ this.mEnabledHosts = enabledHosts;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mEnabledHosts);
+ this.mDisallowLinkHandling = disallowLinkHandling;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
+ /**
+ * List of domains which have been enabled by the user. *
+ */
+ @DataClass.Generated.Member
+ public @NonNull ArraySet<String> getEnabledHosts() {
+ return mEnabledHosts;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isDisallowLinkHandling() {
+ return mDisallowLinkHandling;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
+ mDisallowLinkHandling = value;
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationUserState { " +
+ "userId = " + mUserId + ", " +
+ "enabledHosts = " + mEnabledHosts + ", " +
+ "disallowLinkHandling = " + mDisallowLinkHandling +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationUserState that = (DomainVerificationUserState) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mUserId == that.mUserId
+ && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
+ && mDisallowLinkHandling == that.mDisallowLinkHandling;
+ }
+
+ @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 + mUserId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
+ _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1608234273324L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
+ inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mDisallowLinkHandling\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
new file mode 100644
index 0000000..715d8fb
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
@@ -0,0 +1,125 @@
+/*
+ * 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.verify.domain.proxy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+import com.android.server.pm.verify.domain.DomainVerificationCollector;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+
+import java.util.Objects;
+import java.util.Set;
+
+// TODO(b/170321181): Combine the proxy versions for supporting v1 and v2 at once
+public interface DomainVerificationProxy {
+
+ String TAG = "DomainVerificationProxy";
+
+ boolean DEBUG_PROXIES = false;
+
+ static <ConnectionType extends DomainVerificationProxyV1.Connection
+ & DomainVerificationProxyV2.Connection> DomainVerificationProxy makeProxy(
+ @Nullable ComponentName componentV1, @Nullable ComponentName componentV2,
+ @NonNull Context context, @NonNull DomainVerificationManagerInternal manager,
+ @NonNull DomainVerificationCollector collector, @NonNull ConnectionType connection) {
+ if (DEBUG_PROXIES) {
+ Slog.d(TAG, "Intent filter verification agent: " + componentV1);
+ Slog.d(TAG, "Domain verification agent: " + componentV2);
+ }
+
+ if (componentV2 != null && componentV1 != null
+ && !Objects.equals(componentV2.getPackageName(), componentV1.getPackageName())) {
+ // Only allow a legacy verifier if it's in the same package as the v2 verifier
+ componentV1 = null;
+ }
+
+ DomainVerificationProxy proxyV1 = null;
+ DomainVerificationProxy proxyV2 = null;
+
+ if (componentV1 != null) {
+ proxyV1 = new DomainVerificationProxyV1(context, manager, collector, connection,
+ componentV1);
+ }
+
+ if (componentV2 != null) {
+ proxyV2 = new DomainVerificationProxyV2(context, connection, componentV2);
+ }
+
+ if (proxyV1 != null && proxyV2 != null) {
+ return new DomainVerificationProxyCombined(proxyV1, proxyV2);
+ }
+
+ if (proxyV1 != null) {
+ return proxyV1;
+ }
+
+ if (proxyV2 != null) {
+ return proxyV2;
+ }
+
+ return new DomainVerificationProxyUnavailable();
+ }
+
+ default void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ }
+
+ /**
+ * Runs a message on the caller's Handler as a result of {@link BaseConnection#schedule(int,
+ * Object)}. Abstracts the actual scheduling/running from the manager class. This is also
+ * necessary so that different what codes can be used depending on the verifier proxy on device,
+ * to allow backporting v1. The backport proxy may schedule more or less messages than the v2
+ * proxy.
+ *
+ * @param messageCode One of the values in {@link DomainVerificationMessageCodes}.
+ * @param object Arbitrary object that was originally included.
+ */
+ default boolean runMessage(int messageCode, Object object) {
+ return false;
+ }
+
+ default boolean isCallerVerifier(int callingUid) {
+ return false;
+ }
+
+ @Nullable
+ default ComponentName getComponentName() {
+ return null;
+ }
+
+ interface BaseConnection {
+
+ /**
+ * Schedule something to be run later. The implementation is left up to the caller.
+ *
+ * @param code One of the values in {@link DomainVerificationMessageCodes}.
+ * @param object Arbitrary object to include with the message.
+ */
+ void schedule(int code, @Nullable Object object);
+
+ long getPowerSaveTempWhitelistAppDuration();
+
+ DeviceIdleInternal getDeviceIdleInternal();
+
+ boolean isCallerPackage(int callingUid, @NonNull String packageName);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
new file mode 100644
index 0000000..8571c08
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.verify.domain.proxy;
+
+import android.annotation.NonNull;
+
+import java.util.Set;
+
+class DomainVerificationProxyCombined implements DomainVerificationProxy {
+
+ @NonNull
+ private final DomainVerificationProxy mProxyV1;
+ @NonNull
+ private final DomainVerificationProxy mProxyV2;
+
+ DomainVerificationProxyCombined(@NonNull DomainVerificationProxy proxyV1,
+ @NonNull DomainVerificationProxy proxyV2) {
+ mProxyV1 = proxyV1;
+ mProxyV2 = proxyV2;
+ }
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ mProxyV2.sendBroadcastForPackages(packageNames);
+ mProxyV1.sendBroadcastForPackages(packageNames);
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ // Both proxies must run, so cannot use a direct ||, which may skip the right hand side
+ boolean resultV2 = mProxyV2.runMessage(messageCode, object);
+ boolean resultV1 = mProxyV1.runMessage(messageCode, object);
+ return resultV2 || resultV1;
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return mProxyV2.isCallerVerifier(callingUid) || mProxyV1.isCallerVerifier(callingUid);
+ }
+}
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
similarity index 73%
copy from core/java/android/graphics/fonts/SystemFontState.aidl
copy to services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
index 19b20f2..bd77983 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package android.graphics.fonts;
+package com.android.server.pm.verify.domain.proxy;
-/** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+/** Stub implementation for when the verification agent is unavailable */
+public class DomainVerificationProxyUnavailable implements DomainVerificationProxy {
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
new file mode 100644
index 0000000..eab89e98
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -0,0 +1,257 @@
+/*
+ * 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.verify.domain.proxy;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.BroadcastOptions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.verify.domain.DomainVerificationCollector;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+public class DomainVerificationProxyV1 implements DomainVerificationProxy {
+
+ private static final String TAG = "DomainVerificationProxyV1";
+
+ private static final boolean DEBUG_BROADCASTS = false;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final Connection mConnection;
+
+ @NonNull
+ private final ComponentName mVerifierComponent;
+
+ @NonNull
+ private final DomainVerificationManagerInternal mManager;
+
+ @NonNull
+ private final DomainVerificationCollector mCollector;
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @NonNull
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, Pair<UUID, String>> mRequests = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private int mVerificationToken = 0;
+
+ public DomainVerificationProxyV1(@NonNull Context context,
+ @NonNull DomainVerificationManagerInternal manager,
+ @NonNull DomainVerificationCollector collector, @NonNull Connection connection,
+ @NonNull ComponentName verifierComponent) {
+ mContext = context;
+ mConnection = connection;
+ mVerifierComponent = verifierComponent;
+ mManager = manager;
+ mCollector = collector;
+ }
+
+ public static void queueLegacyVerifyResult(@NonNull Context context,
+ @NonNull DomainVerificationProxyV1.Connection connection, int verificationId,
+ int verificationCode, @Nullable List<String> failedDomains, int callingUid) {
+ context.enforceCallingOrSelfPermission(
+ Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+ "Only the intent filter verification agent can verify applications");
+
+ connection.schedule(DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED,
+ new Response(callingUid, verificationId, verificationCode, failedDomains));
+ }
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ synchronized (mLock) {
+ int size = mRequests.size();
+ for (int index = size - 1; index >= 0; index--) {
+ Pair<UUID, String> pair = mRequests.valueAt(index);
+ if (packageNames.contains(pair.second)) {
+ mRequests.removeAt(index);
+ }
+ }
+ }
+ mConnection.schedule(DomainVerificationMessageCodes.LEGACY_SEND_REQUEST, packageNames);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ switch (messageCode) {
+ case DomainVerificationMessageCodes.LEGACY_SEND_REQUEST:
+ @SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object;
+ if (DEBUG_BROADCASTS) {
+ Slog.d(TAG, "Requesting domain verification for " + packageNames);
+ }
+
+ ArrayMap<Integer, Pair<UUID, String>> newRequests = new ArrayMap<>(
+ packageNames.size());
+ synchronized (mLock) {
+ for (String packageName : packageNames) {
+ UUID domainSetId = mManager.getDomainVerificationInfoId(packageName);
+ if (domainSetId == null) {
+ continue;
+ }
+
+ newRequests.put(mVerificationToken++,
+ Pair.create(domainSetId, packageName));
+ }
+ mRequests.putAll(newRequests);
+ }
+
+ sendBroadcasts(newRequests);
+ return true;
+ case DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED:
+ Response response = (Response) object;
+
+ Pair<UUID, String> pair = mRequests.get(response.verificationId);
+ if (pair == null) {
+ return true;
+ }
+
+ UUID domainSetId = pair.first;
+ String packageName = pair.second;
+ DomainVerificationInfo set;
+ try {
+ set = mManager.getDomainVerificationInfo(packageName);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return true;
+ }
+
+ if (!Objects.equals(domainSetId, set.getIdentifier())) {
+ return true;
+ }
+
+ Set<String> successfulDomains = new ArraySet<>(set.getHostToStateMap().keySet());
+ successfulDomains.removeAll(response.failedDomains);
+
+ int callingUid = response.callingUid;
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ successfulDomains, DomainVerificationState.STATE_SUCCESS);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException ignored) {
+ }
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ new ArraySet<>(response.failedDomains),
+ DomainVerificationState.STATE_LEGACY_FAILURE);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException ignored) {
+ }
+
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return mConnection.isCallerPackage(callingUid, mVerifierComponent.getPackageName());
+ }
+
+ @SuppressWarnings("deprecation")
+ private void sendBroadcasts(@NonNull ArrayMap<Integer, Pair<UUID, String>> verifications) {
+ final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration();
+ mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(),
+ mVerifierComponent.getPackageName(), allowListTimeout,
+ UserHandle.USER_SYSTEM, true, "domain verification agent");
+
+ int size = verifications.size();
+ for (int index = 0; index < size; index++) {
+ int verificationId = verifications.keyAt(index);
+ String packageName = verifications.valueAt(index).second;
+ AndroidPackage pkg = mConnection.getPackage(packageName);
+
+ String hostsString = buildHostsString(pkg);
+
+ Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
+ .setComponent(mVerifierComponent)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
+ verificationId)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
+ IntentFilter.SCHEME_HTTPS)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
+ hostsString)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
+ packageName)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppWhitelistDuration(allowListTimeout);
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle());
+ }
+ }
+
+ @NonNull
+ private String buildHostsString(@NonNull AndroidPackage pkg) {
+ // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
+ // not the version of the verification agent on device.
+ ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+ return TextUtils.join(" ", domains);
+ }
+
+ private static class Response {
+ public final int callingUid;
+ public final int verificationId;
+ public final int verificationCode;
+ @NonNull
+ public final List<String> failedDomains;
+
+ private Response(int callingUid, int verificationId, int verificationCode,
+ @Nullable List<String> failedDomains) {
+ this.callingUid = callingUid;
+ this.verificationId = verificationId;
+ this.verificationCode = verificationCode;
+ this.failedDomains = failedDomains == null ? Collections.emptyList() : failedDomains;
+ }
+ }
+
+ public interface Connection extends BaseConnection {
+
+ @Nullable
+ AndroidPackage getPackage(@NonNull String packageName);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java
new file mode 100644
index 0000000..9fcbce2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java
@@ -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.
+ */
+
+package com.android.server.pm.verify.domain.proxy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.BroadcastOptions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationRequest;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+
+import java.util.Set;
+
+public class DomainVerificationProxyV2 implements DomainVerificationProxy {
+
+ private static final String TAG = "DomainVerificationProxyV2";
+
+ private static final boolean DEBUG_BROADCASTS = true;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final Connection mConnection;
+
+ @NonNull
+ private final ComponentName mVerifierComponent;
+
+ public DomainVerificationProxyV2(@NonNull Context context, @NonNull Connection connection,
+ @NonNull ComponentName verifierComponent) {
+ mContext = context;
+ mConnection = connection;
+ mVerifierComponent = verifierComponent;
+ }
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ mConnection.schedule(com.android.server.pm.verify.domain.DomainVerificationMessageCodes.SEND_REQUEST, packageNames);
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ switch (messageCode) {
+ case DomainVerificationMessageCodes.SEND_REQUEST:
+ @SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object;
+ DomainVerificationRequest request = new DomainVerificationRequest(packageNames);
+
+ final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration();
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppWhitelistDuration(allowListTimeout);
+
+ mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(),
+ mVerifierComponent.getPackageName(), allowListTimeout,
+ UserHandle.USER_SYSTEM, true, "domain verification agent");
+
+ Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION)
+ .setComponent(mVerifierComponent)
+ .putExtra(DomainVerificationManager.EXTRA_VERIFICATION_REQUEST, request)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ if (DEBUG_BROADCASTS) {
+ Slog.d(TAG, "Requesting domain verification for " + packageNames);
+ }
+
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle());
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return mConnection.isCallerPackage(callingUid, mVerifierComponent.getPackageName());
+ }
+
+ @Nullable
+ @Override
+ public ComponentName getComponentName() {
+ return mVerifierComponent;
+ }
+
+ public interface Connection extends BaseConnection {
+ }
+}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index e901f66f..ac358db 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -34,10 +34,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
+import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.policy.devicestate.config.Conditions;
-import com.android.server.policy.devicestate.config.DeviceState;
import com.android.server.policy.devicestate.config.DeviceStateConfig;
import com.android.server.policy.devicestate.config.LidSwitchCondition;
import com.android.server.policy.devicestate.config.NumericRange;
@@ -54,6 +55,7 @@
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
@@ -82,7 +84,7 @@
private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
@VisibleForTesting
- static final int DEFAULT_DEVICE_STATE = 0;
+ static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
@@ -116,31 +118,38 @@
@VisibleForTesting
static DeviceStateProviderImpl createFromConfig(@NonNull Context context,
@Nullable ReadableConfig readableConfig) {
- SparseArray<Conditions> conditionsForState = new SparseArray<>();
+ List<DeviceState> deviceStateList = new ArrayList<>();
+ List<Conditions> conditionsList = new ArrayList<>();
+
if (readableConfig != null) {
DeviceStateConfig config = parseConfig(readableConfig);
if (config != null) {
- for (DeviceState stateConfig : config.getDeviceState()) {
- int state = stateConfig.getIdentifier().intValue();
- Conditions conditions = stateConfig.getConditions();
- conditionsForState.put(state, conditions);
+ for (com.android.server.policy.devicestate.config.DeviceState stateConfig :
+ config.getDeviceState()) {
+ final int state = stateConfig.getIdentifier().intValue();
+ final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
+ deviceStateList.add(new DeviceState(state, name));
+
+ final Conditions condition = stateConfig.getConditions();
+ conditionsList.add(condition);
}
}
}
- if (conditionsForState.size() == 0) {
- conditionsForState.put(DEFAULT_DEVICE_STATE, null);
+ if (deviceStateList.size() == 0) {
+ deviceStateList.add(DEFAULT_DEVICE_STATE);
+ conditionsList.add(null);
}
- return new DeviceStateProviderImpl(context, conditionsForState);
+ return new DeviceStateProviderImpl(context, deviceStateList, conditionsList);
}
// Lock for internal state.
private final Object mLock = new Object();
private final Context mContext;
- // List of supported states in ascending order.
- private final int[] mOrderedStates;
- // Map of state to a boolean supplier that returns true when all required conditions are met for
- // the device to be in the state.
+ // List of supported states in ascending order based on their identifier.
+ private final DeviceState[] mOrderedStates;
+ // Map of state identifier to a boolean supplier that returns true when all required conditions
+ // are met for the device to be in the state.
private final SparseArray<BooleanSupplier> mStateConditions;
@Nullable
@@ -155,12 +164,16 @@
private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
private DeviceStateProviderImpl(@NonNull Context context,
- @NonNull SparseArray<Conditions> conditionsForState) {
+ @NonNull List<DeviceState> deviceStates,
+ @NonNull List<Conditions> stateConditions) {
+ Preconditions.checkArgument(deviceStates.size() == stateConditions.size(),
+ "Number of device states must be equal to the number of device state conditions.");
+
mContext = context;
- mOrderedStates = new int[conditionsForState.size()];
- for (int i = 0; i < conditionsForState.size(); i++) {
- mOrderedStates[i] = conditionsForState.keyAt(i);
- }
+
+ DeviceState[] orderedStates = deviceStates.toArray(new DeviceState[deviceStates.size()]);
+ Arrays.sort(orderedStates, Comparator.comparingInt(DeviceState::getIdentifier));
+ mOrderedStates = orderedStates;
// Whether or not this instance should register to receive lid switch notifications from
// InputManagerInternal. If there are no device state conditions that are based on the lid
@@ -171,9 +184,9 @@
final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>();
mStateConditions = new SparseArray<>();
- for (int i = 0; i < mOrderedStates.length; i++) {
- int state = mOrderedStates[i];
- Conditions conditions = conditionsForState.get(state);
+ for (int i = 0; i < stateConditions.size(); i++) {
+ final int state = deviceStates.get(i).getIdentifier();
+ final Conditions conditions = stateConditions.get(i);
if (conditions == null) {
mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
continue;
@@ -261,7 +274,7 @@
/** Notifies the listener that the set of supported device states has changed. */
private void notifySupportedStatesChanged() {
- int[] supportedStates;
+ DeviceState[] supportedStates;
synchronized (mLock) {
if (mListener == null) {
return;
@@ -281,9 +294,9 @@
return;
}
- int newState = mOrderedStates[0];
+ int newState = mOrderedStates[0].getIdentifier();
for (int i = 0; i < mOrderedStates.length; i++) {
- int state = mOrderedStates[i];
+ int state = mOrderedStates[i].getIdentifier();
if (mStateConditions.get(state).getAsBoolean()) {
newState = state;
break;
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
index ff51237..c10e828 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldController.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -209,16 +209,23 @@
* resource.
*/
private class DeviceStateListener implements DeviceStateManager.DeviceStateListener {
- private final int mFoldedDeviceState;
+ private final int[] mFoldedDeviceStates;
DeviceStateListener(Context context) {
- mFoldedDeviceState = context.getResources().getInteger(
- com.android.internal.R.integer.config_foldedDeviceState);
+ mFoldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
}
@Override
public void onDeviceStateChanged(int deviceState) {
- setDeviceFolded(deviceState == mFoldedDeviceState);
+ boolean folded = false;
+ for (int i = 0; i < mFoldedDeviceStates.length; i++) {
+ if (deviceState == mFoldedDeviceStates[i]) {
+ folded = true;
+ break;
+ }
+ }
+ setDeviceFolded(folded);
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index db4b6d0..8e0d632 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2892,8 +2892,8 @@
PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- mDisplayReady = mDisplayManagerInternal.requestPowerState(displayPowerRequest,
- mRequestWaitForNegativeProximity);
+ mDisplayReady = mDisplayManagerInternal.requestPowerState(Display.DEFAULT_DISPLAY_GROUP,
+ displayPowerRequest, mRequestWaitForNegativeProximity);
mRequestWaitForNegativeProximity = false;
if (DEBUG_SPEW) {
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
index eb9df75..9a91848 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
@@ -120,7 +120,7 @@
* @return List of EnergyMeasurement objects containing energy measurements for all
* available energy meters.
*/
- android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters(int[] channelIds);
+ android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(int[] channelIds);
/**
* Returns boolean indicating if connection to power stats HAL was established.
@@ -235,13 +235,13 @@
}
@Override
- public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters(int[] channelIds) {
+ public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
android.hardware.power.stats.EnergyMeasurement[] energyMeasurementHAL = null;
if (sVintfPowerStats != null) {
try {
energyMeasurementHAL =
- sVintfPowerStats.get().readEnergyMeters(channelIds);
+ sVintfPowerStats.get().readEnergyMeter(channelIds);
} catch (RemoteException e) {
if (DEBUG) Slog.d(TAG, "Failed to get energy measurements from PowerStats HAL");
}
@@ -311,7 +311,7 @@
}
@Override
- public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters(int[] channelIds) {
+ public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
return nativeReadEnergyMeters(channelIds);
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 78a227e..e117b0c 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -69,7 +69,7 @@
// Log power meter data.
EnergyMeasurement[] energyMeasurements =
- mPowerStatsHALWrapper.readEnergyMeters(new int[0]);
+ mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
mPowerStatsMeterStorage.write(
EnergyMeasurementUtils.getProtoBytes(energyMeasurements));
if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements);
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 5fe5db6..ea41980 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -21,7 +21,9 @@
import android.hardware.power.stats.Channel;
import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyMeasurement;
import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.StateResidencyResult;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -245,10 +247,50 @@
future, energyConsumerIds));
return future;
}
+
+ @Override
+ public PowerEntity[] getPowerEntityInfo() {
+ return getPowerStatsHal().getPowerEntityInfo();
+ }
+
+ @Override
+ public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
+ int[] powerEntityIds) {
+ final CompletableFuture<StateResidencyResult[]> future = new CompletableFuture<>();
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(PowerStatsService.this::getStateResidencyAsync,
+ future, powerEntityIds));
+ return future;
+ }
+
+ @Override
+ public Channel[] getEnergyMeterInfo() {
+ return getPowerStatsHal().getEnergyMeterInfo();
+ }
+
+ @Override
+ public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
+ int[] channelIds) {
+ final CompletableFuture<EnergyMeasurement[]> future = new CompletableFuture<>();
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(PowerStatsService.this::readEnergyMeterAsync,
+ future, channelIds));
+ return future;
+ }
}
private void getEnergyConsumedAsync(CompletableFuture<EnergyConsumerResult[]> future,
int[] energyConsumerIds) {
future.complete(getPowerStatsHal().getEnergyConsumed(energyConsumerIds));
}
+
+ private void getStateResidencyAsync(CompletableFuture<StateResidencyResult[]> future,
+ int[] powerEntityIds) {
+ future.complete(getPowerStatsHal().getStateResidency(powerEntityIds));
+ }
+
+ private void readEnergyMeterAsync(CompletableFuture<EnergyMeasurement[]> future,
+ int[] channelIds) {
+ future.complete(getPowerStatsHal().readEnergyMeter(channelIds));
+ }
}
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
index f8b9601..7c6999a 100644
--- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -70,7 +70,7 @@
}
private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) {
- EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeters(new int[0]);
+ EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
if (energyMeasurements == null) {
return StatsManager.PULL_SKIP;
}
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
index 8a1c778..3dbc32a 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
@@ -159,7 +159,7 @@
final Intent intent = new Intent(
RotationResolverService.SERVICE_INTERFACE).setPackage(resolvedPackage);
- final ResolveInfo resolveInfo = context.getPackageManager().resolveActivityAsUser(intent,
+ final ResolveInfo resolveInfo = context.getPackageManager().resolveServiceAsUser(intent,
flags, context.getUserId());
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
Slog.wtf(TAG, String.format("Service %s not found in package %s",
diff --git a/services/core/java/com/android/server/timedetector/DeviceConfig.java b/services/core/java/com/android/server/timedetector/DeviceConfig.java
new file mode 100644
index 0000000..7b9ad0f
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/DeviceConfig.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.timedetector;
+
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+
+import java.time.Duration;
+import java.util.concurrent.Executor;
+
+/**
+ * A helper class for reading / monitoring the {@link
+ * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags.
+ */
+public final class DeviceConfig {
+
+ /**
+ * An annotation used to indicate when a {@link
+ * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required.
+ *
+ * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider
+ * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the
+ * prefix "geotz_" on all of its key strings.
+ */
+ @StringDef(prefix = "KEY_", value = {
+ KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED,
+ KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT,
+ KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
+ KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
+ })
+ @interface DeviceConfigKey {}
+
+ /**
+ * The key to force location time zone detection on for a device. Only intended for use during
+ * release testing with droidfooders. The user can still disable the feature by turning off the
+ * master location switch, or disabling automatic time zone detection.
+ */
+ @DeviceConfigKey
+ public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED =
+ "force_location_time_zone_detection_enabled";
+
+ /**
+ * The key for the default value used to determine whether location time zone detection is
+ * enabled when the user hasn't explicitly set it yet.
+ */
+ @DeviceConfigKey
+ public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT =
+ "location_time_zone_detection_enabled_default";
+
+ /**
+ * The key for the minimum delay after location time zone detection has been enabled before the
+ * location time zone manager can report it is uncertain about the time zone.
+ */
+ @DeviceConfigKey
+ public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS =
+ "location_time_zone_detection_uncertainty_delay_millis";
+
+ /**
+ * The key for the timeout passed to a location time zone provider that tells it how long it has
+ * to provide an explicit first suggestion without being declared uncertain.
+ */
+ @DeviceConfigKey
+ public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS =
+ "ltpz_init_timeout_millis";
+
+ /**
+ * The key for the extra time added to {@link
+ * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone
+ * manager before the location time zone provider will actually be declared uncertain.
+ */
+ @DeviceConfigKey
+ public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS =
+ "ltpz_init_timeout_fuzz_millis";
+
+ /** Creates an instance. */
+ public DeviceConfig() {}
+
+ /** Adds a listener for the system_time namespace. */
+ public void addListener(
+ @NonNull Executor handlerExecutor, @NonNull Runnable listener) {
+ android.provider.DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_SYSTEM_TIME,
+ handlerExecutor,
+ properties -> listener.run());
+ }
+
+ /**
+ * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time
+ * namespace, or {@code defaultValue} if there is no explicit value set.
+ */
+ public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) {
+ return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue);
+ }
+
+ /**
+ * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time
+ * namespace, or {@code defaultValue} if there is no explicit value set.
+ */
+ @Nullable
+ public Duration getDurationFromMillis(
+ @DeviceConfigKey String key, @Nullable Duration defaultValue) {
+ long deviceConfigValue =
+ android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1);
+ if (deviceConfigValue < 0) {
+ return defaultValue;
+ }
+ return Duration.ofMillis(deviceConfigValue);
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
similarity index 91%
rename from services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
rename to services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4f3f9dc..5cd1718 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -25,7 +25,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
-import android.os.Environment;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -39,11 +38,11 @@
import java.util.Objects;
/**
- * The real implementation of {@link TimeDetectorStrategyImpl.Callback} used on device.
+ * The real implementation of {@link TimeDetectorStrategyImpl.Environment} used on device.
*/
-public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrategyImpl.Callback {
+public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
- private final static String TAG = "timedetector.TimeDetectorStrategyCallbackImpl";
+ private static final String TAG = TimeDetectorService.TAG;
private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
@@ -52,7 +51,7 @@
* incorrect for sure.
*/
private static final Instant TIME_LOWER_BOUND = Instant.ofEpochMilli(
- Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
+ Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
/**
* By default telephony and network only suggestions are accepted and telephony takes
@@ -74,7 +73,7 @@
@NonNull private final AlarmManager mAlarmManager;
@NonNull private final int[] mOriginPriorities;
- public TimeDetectorStrategyCallbackImpl(@NonNull Context context) {
+ public EnvironmentImpl(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
mContentResolver = Objects.requireNonNull(context.getContentResolver());
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 71b1a49..bbbd19f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -50,7 +50,7 @@
* implementation to deal with the logic around time detection.
*/
public final class TimeDetectorService extends ITimeDetectorService.Stub {
- private static final String TAG = "TimeDetectorService";
+ static final String TAG = "time_detector";
public static class Lifecycle extends SystemService {
@@ -73,8 +73,8 @@
@NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
private static TimeDetectorService create(@NonNull Context context) {
- TimeDetectorStrategyImpl.Callback callback = new TimeDetectorStrategyCallbackImpl(context);
- TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl(callback);
+ TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context);
+ TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl(environment);
Handler handler = FgThread.getHandler();
TimeDetectorService timeDetectorService =
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 2fbeb75..7cd4184 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -40,6 +40,7 @@
import java.time.Instant;
import java.util.Arrays;
+import java.util.Objects;
/**
* An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to
@@ -51,7 +52,7 @@
public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
private static final boolean DBG = false;
- private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
+ private static final String LOG_TAG = TimeDetectorService.TAG;
/** A score value used to indicate "no score", either due to validation failure or age. */
private static final int TELEPHONY_INVALID_SCORE = -1;
@@ -88,7 +89,7 @@
private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
@NonNull
- private final Callback mCallback;
+ private final Environment mEnvironment;
// Used to store the last time the system clock state was set automatically. It is used to
// detect (and log) issues with the realtime clock or whether the clock is being set without
@@ -127,7 +128,7 @@
* moved to {@link TimeDetectorStrategy}. There are similar issues with
* {@link #systemClockMillis()} while any process can modify the system clock.
*/
- public interface Callback {
+ public interface Environment {
/**
* The absolute threshold below which the system clock need not be updated. i.e. if setting
@@ -170,8 +171,8 @@
void releaseWakeLock();
}
- TimeDetectorStrategyImpl(@NonNull Callback callback) {
- mCallback = callback;
+ TimeDetectorStrategyImpl(@NonNull Environment environment) {
+ mEnvironment = Objects.requireNonNull(environment);
}
@Override
@@ -267,7 +268,7 @@
@Override
public synchronized void handleAutoTimeConfigChanged() {
- boolean enabled = mCallback.isAutoTimeDetectionEnabled();
+ boolean enabled = mEnvironment.isAutoTimeDetectionEnabled();
// When automatic time detection is enabled we update the system clock instantly if we can.
// Conversely, when automatic time detection is disabled we leave the clock as it is.
if (enabled) {
@@ -286,20 +287,20 @@
ipw.increaseIndent(); // level 1
ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
- ipw.println("mCallback.isAutoTimeDetectionEnabled()="
- + mCallback.isAutoTimeDetectionEnabled());
- ipw.println("mCallback.elapsedRealtimeMillis()=" + mCallback.elapsedRealtimeMillis());
- ipw.println("mCallback.systemClockMillis()=" + mCallback.systemClockMillis());
- ipw.println("mCallback.systemClockUpdateThresholdMillis()="
- + mCallback.systemClockUpdateThresholdMillis());
- ipw.printf("mCallback.autoTimeLowerBound()=%s(%s)\n",
- mCallback.autoTimeLowerBound(),
- mCallback.autoTimeLowerBound().toEpochMilli());
+ ipw.println("mEnvironment.isAutoTimeDetectionEnabled()="
+ + mEnvironment.isAutoTimeDetectionEnabled());
+ ipw.println("mEnvironment.elapsedRealtimeMillis()=" + mEnvironment.elapsedRealtimeMillis());
+ ipw.println("mEnvironment.systemClockMillis()=" + mEnvironment.systemClockMillis());
+ ipw.println("mEnvironment.systemClockUpdateThresholdMillis()="
+ + mEnvironment.systemClockUpdateThresholdMillis());
+ ipw.printf("mEnvironment.autoTimeLowerBound()=%s(%s)\n",
+ mEnvironment.autoTimeLowerBound(),
+ mEnvironment.autoTimeLowerBound().toEpochMilli());
String priorities =
- Arrays.stream(mCallback.autoOriginPriorities())
+ Arrays.stream(mEnvironment.autoOriginPriorities())
.mapToObj(TimeDetectorStrategy::originToString)
.collect(joining(",", "[", "]"));
- ipw.println("mCallback.autoOriginPriorities()=" + priorities);
+ ipw.println("mEnvironment.autoOriginPriorities()=" + priorities);
ipw.println("Time change log:");
ipw.increaseIndent(); // level 2
@@ -372,7 +373,7 @@
}
// We can validate the suggestion against the reference time clock.
- long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+ long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
if (elapsedRealtimeMillis < newUtcTime.getReferenceTimeMillis()) {
// elapsedRealtime clock went backwards?
Slog.w(LOG_TAG, "New reference time is in the future? Ignoring."
@@ -391,7 +392,7 @@
private boolean validateSuggestionAgainstLowerBound(
@NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion) {
- Instant lowerBound = mCallback.autoTimeLowerBound();
+ Instant lowerBound = mEnvironment.autoTimeLowerBound();
// Suggestion is definitely wrong if it comes before lower time bound.
if (lowerBound.isAfter(Instant.ofEpochMilli(newUtcTime.getValue()))) {
@@ -405,13 +406,13 @@
@GuardedBy("this")
private void doAutoTimeDetection(@NonNull String detectionReason) {
- if (!mCallback.isAutoTimeDetectionEnabled()) {
+ if (!mEnvironment.isAutoTimeDetectionEnabled()) {
// Avoid doing unnecessary work with this (race-prone) check.
return;
}
// Try the different origins one at a time.
- int[] originPriorities = mCallback.autoOriginPriorities();
+ int[] originPriorities = mEnvironment.autoOriginPriorities();
for (int origin : originPriorities) {
TimestampedValue<Long> newUtcTime = null;
String cause = null;
@@ -470,7 +471,7 @@
@GuardedBy("this")
@Nullable
private TelephonyTimeSuggestion findBestTelephonySuggestion() {
- long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+ long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
// Telephony time suggestions are assumed to be derived from NITZ or NITZ-like signals.
// These have a number of limitations:
@@ -579,7 +580,7 @@
}
TimestampedValue<Long> utcTime = networkSuggestion.getUtcTime();
- long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+ long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
// The latest suggestion is not valid, usually due to its age.
return null;
@@ -599,7 +600,7 @@
}
TimestampedValue<Long> utcTime = gnssTimeSuggestion.getUtcTime();
- long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+ long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
// The latest suggestion is not valid, usually due to its age.
return null;
@@ -619,7 +620,7 @@
}
TimestampedValue<Long> utcTime = externalTimeSuggestion.getUtcTime();
- long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+ long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
// The latest suggestion is not valid, usually due to its age.
return null;
@@ -634,7 +635,7 @@
boolean isOriginAutomatic = isOriginAutomatic(origin);
if (isOriginAutomatic) {
- if (!mCallback.isAutoTimeDetectionEnabled()) {
+ if (!mEnvironment.isAutoTimeDetectionEnabled()) {
if (DBG) {
Slog.d(LOG_TAG, "Auto time detection is not enabled."
+ " origin=" + originToString(origin)
@@ -644,7 +645,7 @@
return false;
}
} else {
- if (mCallback.isAutoTimeDetectionEnabled()) {
+ if (mEnvironment.isAutoTimeDetectionEnabled()) {
if (DBG) {
Slog.d(LOG_TAG, "Auto time detection is enabled."
+ " origin=" + originToString(origin)
@@ -655,11 +656,11 @@
}
}
- mCallback.acquireWakeLock();
+ mEnvironment.acquireWakeLock();
try {
return setSystemClockUnderWakeLock(origin, time, cause);
} finally {
- mCallback.releaseWakeLock();
+ mEnvironment.releaseWakeLock();
}
}
@@ -671,9 +672,9 @@
private boolean setSystemClockUnderWakeLock(
@Origin int origin, @NonNull TimestampedValue<Long> newTime, @NonNull String cause) {
- long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+ long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
boolean isOriginAutomatic = isOriginAutomatic(origin);
- long actualSystemClockMillis = mCallback.systemClockMillis();
+ long actualSystemClockMillis = mEnvironment.systemClockMillis();
if (isOriginAutomatic) {
// CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
// may be setting the clock.
@@ -701,7 +702,7 @@
// Check if the new signal would make sufficient difference to the system clock. If it's
// below the threshold then ignore it.
long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
- long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
+ long systemClockUpdateThreshold = mEnvironment.systemClockUpdateThresholdMillis();
if (absTimeDifference < systemClockUpdateThreshold) {
if (DBG) {
Slog.d(LOG_TAG, "Not setting system clock. New time and"
@@ -715,7 +716,7 @@
return true;
}
- mCallback.setSystemClock(newSystemClockMillis);
+ mEnvironment.setSystemClock(newSystemClockMillis);
String logMsg = "Set system clock using time=" + newTime
+ " cause=" + cause
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
similarity index 78%
rename from services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
rename to services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 1357608..f52b9b1 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -33,43 +33,52 @@
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
+import com.android.server.timedetector.DeviceConfig;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
- * The real implementation of {@link TimeZoneDetectorStrategyImpl.Callback}.
+ * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
*/
-public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
+public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
- private static final String LOG_TAG = "TimeZoneDetectorCallbackImpl";
+ private static final String LOG_TAG = TimeZoneDetectorService.TAG;
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final ContentResolver mCr;
@NonNull private final UserManager mUserManager;
- @NonNull private final boolean mGeoDetectionFeatureEnabled;
+ @NonNull private final DeviceConfig mDeviceConfig;
+ @NonNull private final boolean mGeoDetectionSupported;
@NonNull private final LocationManager mLocationManager;
+
// @NonNull after setConfigChangeListener() is called.
+ @GuardedBy("this")
private ConfigurationChangeListener mConfigChangeListener;
- TimeZoneDetectorCallbackImpl(@NonNull Context context, @NonNull Handler handler,
- boolean geoDetectionFeatureEnabled) {
+ EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
+ @NonNull DeviceConfig deviceConfig, boolean geoDetectionSupported) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
+ Executor handlerExecutor = new HandlerExecutor(mHandler);
mCr = context.getContentResolver();
mUserManager = context.getSystemService(UserManager.class);
mLocationManager = context.getSystemService(LocationManager.class);
- mGeoDetectionFeatureEnabled = geoDetectionFeatureEnabled;
+ mDeviceConfig = deviceConfig;
+ mGeoDetectionSupported = geoDetectionSupported;
- // Wire up the change listener. All invocations are performed on the mHandler thread.
+ // Wire up the change listeners. All invocations are performed on the mHandler thread.
// Listen for the user changing / the user's location mode changing.
IntentFilter filter = new IntentFilter();
@@ -103,18 +112,29 @@
handleConfigChangeOnHandlerThread();
}
}, UserHandle.USER_ALL);
+
+ // Add async callbacks for changes to server-side flags: some of the flags affect device /
+ // user config. All changes can be treated like a config change. If flags that affect config
+ // haven't changed then call will be a no-op.
+ mDeviceConfig.addListener(
+ handlerExecutor,
+ this::handleConfigChangeOnHandlerThread);
}
private void handleConfigChangeOnHandlerThread() {
- if (mConfigChangeListener == null) {
- Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+ synchronized (this) {
+ if (mConfigChangeListener == null) {
+ Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+ }
+ mConfigChangeListener.onChange();
}
- mConfigChangeListener.onChange();
}
@Override
public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
- mConfigChangeListener = Objects.requireNonNull(listener);
+ synchronized (this) {
+ mConfigChangeListener = Objects.requireNonNull(listener);
+ }
}
@Override
@@ -174,7 +194,10 @@
// time zone detection: if we wrote it down then we'd set the value explicitly, which
// would prevent detecting "default" later. That might influence what happens on later
// releases that support geo detection on the same hardware.
- if (isGeoDetectionSupported()) {
+ // Also avoid writing the geo detection enabled setting for devices that are currently
+ // force-enabled: otherwise we might overwrite a droidfood user's real setting
+ // permanently.
+ if (isGeoDetectionSupported() && !isGeoDetectionForceEnabled()) {
final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled);
}
@@ -191,7 +214,7 @@
}
private boolean isGeoDetectionSupported() {
- return mGeoDetectionFeatureEnabled;
+ return mGeoDetectionSupported;
}
private boolean isAutoDetectionEnabled() {
@@ -213,12 +236,25 @@
}
private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
- final boolean geoDetectionEnabledByDefault = false;
+ // We may never use this, but it gives us a way to force location-based time zone detection
+ // on for testers (where their other settings allow).
+ boolean forceEnabled = isGeoDetectionForceEnabled();
+ if (forceEnabled) {
+ return true;
+ }
+
+ final boolean geoDetectionEnabledByDefault = mDeviceConfig.getBoolean(
+ DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, false);
return Settings.Secure.getIntForUser(mCr,
Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
(geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0;
}
+ private boolean isGeoDetectionForceEnabled() {
+ return mDeviceConfig.getBoolean(
+ DeviceConfig.KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, false);
+ }
+
private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) {
// See comment in setAutoDetectionEnabledIfRequired. http://b/171953500
if (isGeoDetectionEnabled(userId) != enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
index b63df05..4eb1b99 100644
--- a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
+++ b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
@@ -19,8 +19,11 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
+import java.time.Duration;
import java.util.ArrayDeque;
/**
@@ -49,18 +52,15 @@
*/
public final class ReferenceWithHistory<V> {
- private static final Object NULL_MARKER = "{null marker}";
-
/** The maximum number of references to store. */
private final int mMaxHistorySize;
- /**
- * The history storage. Note that ArrayDeque doesn't support {@code null} so this stores Object
- * and not V. Use {@link #packNullIfRequired(Object)} and {@link #unpackNullIfRequired(Object)}
- * to convert to / from the storage object.
- */
+ /** The number of times {@link #set(Object)} has been called. */
+ private int mSetCount;
+
+ /** The history storage. */
@Nullable
- private ArrayDeque<Object> mValues;
+ private ArrayDeque<TimestampedValue<V>> mValues;
/**
* Creates an instance that records, at most, the specified number of values.
@@ -78,8 +78,8 @@
if (mValues == null || mValues.isEmpty()) {
return null;
}
- Object value = mValues.getFirst();
- return unpackNullIfRequired(value);
+ TimestampedValue<V> valueHolder = mValues.getFirst();
+ return valueHolder.getValue();
}
/**
@@ -98,8 +98,10 @@
V previous = get();
- Object nullSafeValue = packNullIfRequired(newValue);
- mValues.addFirst(nullSafeValue);
+ TimestampedValue<V> valueHolder =
+ new TimestampedValue<>(SystemClock.elapsedRealtime(), newValue);
+ mValues.addFirst(valueHolder);
+ mSetCount++;
return previous;
}
@@ -110,10 +112,13 @@
if (mValues == null) {
ipw.println("{Empty}");
} else {
- int i = 0;
- for (Object value : mValues) {
- ipw.println(i + ": " + unpackNullIfRequired(value));
- i++;
+ int i = mSetCount;
+ for (TimestampedValue<V> valueHolder : mValues) {
+ ipw.print(--i);
+ ipw.print("@");
+ ipw.print(Duration.ofMillis(valueHolder.getReferenceTimeMillis()).toString());
+ ipw.print(": ");
+ ipw.println(valueHolder.getValue());
}
}
ipw.flush();
@@ -130,23 +135,4 @@
public String toString() {
return String.valueOf(get());
}
-
- /**
- * Turns a non-nullable Object into a nullable value. See also
- * {@link #packNullIfRequired(Object)}.
- */
- @SuppressWarnings("unchecked")
- @Nullable
- private V unpackNullIfRequired(@NonNull Object value) {
- return value == NULL_MARKER ? null : (V) value;
- }
-
- /**
- * Turns a nullable value into a non-nullable Object. See also
- * {@link #unpackNullIfRequired(Object)}.
- */
- @NonNull
- private Object packNullIfRequired(@Nullable V value) {
- return value == null ? NULL_MARKER : value;
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 2ead3be..bd71ddf 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -59,7 +59,7 @@
public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
implements IBinder.DeathRecipient {
- private static final String TAG = "TimeZoneDetectorService";
+ static final String TAG = "time_zone_detector";
/**
* A "feature switch" for location-based time zone detection. If this is {@code false}. It is
@@ -67,19 +67,19 @@
* is important.
*/
@Nullable
- private static Boolean sGeoLocationTimeZoneDetectionEnabled;
+ private static Boolean sGeoLocationTimeZoneDetectionSupported;
/** Returns {@code true} if the location-based time zone detection feature is enabled. */
- public static boolean isGeoLocationTimeZoneDetectionEnabled(Context context) {
- if (sGeoLocationTimeZoneDetectionEnabled == null) {
+ public static boolean isGeoLocationTimeZoneDetectionSupported(Context context) {
+ if (sGeoLocationTimeZoneDetectionSupported == null) {
// The config value is expected to be the main switch. Platform developers can also
// enable the feature using a persistent system property.
- sGeoLocationTimeZoneDetectionEnabled = context.getResources().getBoolean(
+ sGeoLocationTimeZoneDetectionSupported = context.getResources().getBoolean(
com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection)
|| SystemProperties.getBoolean(
"persist.sys.location_time_zone_detection_feature_enabled", false);
}
- return sGeoLocationTimeZoneDetectionEnabled;
+ return sGeoLocationTimeZoneDetectionSupported;
}
/**
@@ -98,11 +98,11 @@
Context context = getContext();
Handler handler = FgThread.getHandler();
- boolean geolocationTimeZoneDetectionEnabled =
- isGeoLocationTimeZoneDetectionEnabled(context);
+ boolean geolocationTimeZoneDetectionSupported =
+ isGeoLocationTimeZoneDetectionSupported(context);
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
TimeZoneDetectorStrategyImpl.create(
- context, handler, geolocationTimeZoneDetectionEnabled);
+ context, handler, geolocationTimeZoneDetectionSupported);
// Create and publish the local service for use by internal callers.
TimeZoneDetectorInternal internal =
@@ -330,7 +330,7 @@
boolean isGeoTimeZoneDetectionSupported() {
enforceManageTimeZoneDetectorPermission();
- return isGeoLocationTimeZoneDetectionEnabled(mContext);
+ return isGeoLocationTimeZoneDetectionSupported(mContext);
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 781668b..c464b74 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -38,6 +38,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.timedetector.DeviceConfig;
import java.util.ArrayList;
import java.util.List;
@@ -59,7 +60,7 @@
* conditions.
*/
@VisibleForTesting
- public interface Callback {
+ public interface Environment {
/**
* Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
@@ -97,7 +98,7 @@
void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfiguration);
}
- private static final String LOG_TAG = "TimeZoneDetectorStrategy";
+ private static final String LOG_TAG = TimeZoneDetectorService.TAG;
private static final boolean DBG = false;
/**
@@ -164,7 +165,7 @@
private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
@NonNull
- private final Callback mCallback;
+ private final Environment mEnvironment;
@GuardedBy("this")
@NonNull
@@ -203,17 +204,18 @@
*/
public static TimeZoneDetectorStrategyImpl create(
@NonNull Context context, @NonNull Handler handler,
- boolean geolocationTimeZoneDetectionEnabled) {
+ boolean geoDetectionSupported) {
- TimeZoneDetectorCallbackImpl callback = new TimeZoneDetectorCallbackImpl(
- context, handler, geolocationTimeZoneDetectionEnabled);
- return new TimeZoneDetectorStrategyImpl(callback);
+ DeviceConfig deviceConfig = new DeviceConfig();
+ EnvironmentImpl environment = new EnvironmentImpl(
+ context, handler, deviceConfig, geoDetectionSupported);
+ return new TimeZoneDetectorStrategyImpl(environment);
}
@VisibleForTesting
- public TimeZoneDetectorStrategyImpl(@NonNull Callback callback) {
- mCallback = Objects.requireNonNull(callback);
- mCallback.setConfigChangeListener(this::handleConfigChanged);
+ public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
+ mEnvironment = Objects.requireNonNull(environment);
+ mEnvironment.setConfigChangeListener(this::handleConfigChanged);
}
/**
@@ -230,13 +232,13 @@
@Override
@NonNull
public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
- return mCallback.getConfigurationInternal(userId);
+ return mEnvironment.getConfigurationInternal(userId);
}
@Override
@NonNull
public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() {
- int currentUserId = mCallback.getCurrentUserId();
+ int currentUserId = mEnvironment.getCurrentUserId();
return getConfigurationInternal(currentUserId);
}
@@ -257,9 +259,9 @@
return false;
}
- // Store the configuration / notify as needed. This will cause the mCallback to invoke
+ // Store the configuration / notify as needed. This will cause the mEnvironment to invoke
// handleConfigChanged() asynchronously.
- mCallback.storeConfiguration(userId, newConfiguration);
+ mEnvironment.storeConfiguration(userId, newConfiguration);
String logMsg = "Configuration changed:"
+ " oldConfiguration=" + oldConfiguration
@@ -275,8 +277,9 @@
public synchronized void suggestGeolocationTimeZone(
@NonNull GeolocationTimeZoneSuggestion suggestion) {
- int currentUserId = mCallback.getCurrentUserId();
- ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+ int currentUserId = mEnvironment.getCurrentUserId();
+ ConfigurationInternal currentUserConfig =
+ mEnvironment.getConfigurationInternal(currentUserId);
if (DBG) {
Slog.d(LOG_TAG, "Geolocation suggestion received."
+ " currentUserConfig=" + currentUserConfig
@@ -299,7 +302,7 @@
public synchronized boolean suggestManualTimeZone(
@UserIdInt int userId, @NonNull ManualTimeZoneSuggestion suggestion) {
- int currentUserId = mCallback.getCurrentUserId();
+ int currentUserId = mEnvironment.getCurrentUserId();
if (userId != currentUserId) {
Slog.w(LOG_TAG, "Manual suggestion received but user != current user, userId=" + userId
+ " suggestion=" + suggestion);
@@ -332,8 +335,9 @@
public synchronized void suggestTelephonyTimeZone(
@NonNull TelephonyTimeZoneSuggestion suggestion) {
- int currentUserId = mCallback.getCurrentUserId();
- ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+ int currentUserId = mEnvironment.getCurrentUserId();
+ ConfigurationInternal currentUserConfig =
+ mEnvironment.getConfigurationInternal(currentUserId);
if (DBG) {
Slog.d(LOG_TAG, "Telephony suggestion received. currentUserConfig=" + currentUserConfig
+ " newSuggestion=" + suggestion);
@@ -423,7 +427,7 @@
String zoneId;
// Introduce bias towards the device's current zone when there are multiple zone suggested.
- String deviceTimeZone = mCallback.getDeviceTimeZone();
+ String deviceTimeZone = mEnvironment.getDeviceTimeZone();
if (zoneIds.contains(deviceTimeZone)) {
if (DBG) {
Slog.d(LOG_TAG,
@@ -486,7 +490,7 @@
@GuardedBy("this")
private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
- String currentZoneId = mCallback.getDeviceTimeZone();
+ String currentZoneId = mEnvironment.getDeviceTimeZone();
// Avoid unnecessary changes / intents.
if (newZoneId.equals(currentZoneId)) {
@@ -501,7 +505,7 @@
return;
}
- mCallback.setDeviceTimeZone(newZoneId);
+ mEnvironment.setDeviceTimeZone(newZoneId);
String msg = "Set device time zone."
+ ", currentZoneId=" + currentZoneId
+ ", newZoneId=" + newZoneId
@@ -572,8 +576,9 @@
// This method is called whenever the user changes or the config for any user changes. We
// don't know what happened, so we capture the current user's config, check to see if we
// need to clear state associated with a previous user, and rerun detection.
- int currentUserId = mCallback.getCurrentUserId();
- ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+ int currentUserId = mEnvironment.getCurrentUserId();
+ ConfigurationInternal currentUserConfig =
+ mEnvironment.getConfigurationInternal(currentUserId);
GeolocationTimeZoneSuggestion latestGeoLocationSuggestion =
mLatestGeoLocationSuggestion.get();
@@ -603,14 +608,14 @@
ipw.println("TimeZoneDetectorStrategy:");
ipw.increaseIndent(); // level 1
- int currentUserId = mCallback.getCurrentUserId();
- ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
- ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
- ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
+ int currentUserId = mEnvironment.getCurrentUserId();
+ ipw.println("mEnvironment.getCurrentUserId()=" + currentUserId);
+ ConfigurationInternal configuration = mEnvironment.getConfigurationInternal(currentUserId);
+ ipw.println("mEnvironment.getConfiguration(currentUserId)=" + configuration);
ipw.println("[Capabilities=" + configuration.createCapabilitiesAndConfig() + "]");
- ipw.println("mCallback.isDeviceTimeZoneInitialized()="
- + mCallback.isDeviceTimeZoneInitialized());
- ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
+ ipw.println("mEnvironment.isDeviceTimeZoneInitialized()="
+ + mEnvironment.isDeviceTimeZoneInitialized());
+ ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
ipw.println("Time zone change log:");
ipw.increaseIndent(); // level 2
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 83b33ee..a54288f 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import com.android.server.LocalServices;
+import com.android.server.timedetector.DeviceConfig;
import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
@@ -32,18 +33,22 @@
*/
class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
- private static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
- private static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
- private static final Duration PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+ private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+ private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
+ Duration.ofMinutes(1);
+ private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
@NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
@NonNull private final LocationTimeZoneProviderController mController;
+ @NonNull private final DeviceConfig mDeviceConfig;
@NonNull private final ConfigurationChangeListener mConfigurationChangeListener;
ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
+ @NonNull DeviceConfig deviceConfig,
@NonNull LocationTimeZoneProviderController controller) {
super(threadingDomain);
mController = Objects.requireNonNull(controller);
+ mDeviceConfig = Objects.requireNonNull(deviceConfig);
mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class);
// Listen for configuration changes.
@@ -66,18 +71,24 @@
@Override
@NonNull
Duration getProviderInitializationTimeout() {
- return PROVIDER_INITIALIZATION_TIMEOUT;
+ return mDeviceConfig.getDurationFromMillis(
+ DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
+ DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT);
}
@Override
@NonNull
Duration getProviderInitializationTimeoutFuzz() {
- return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
+ return mDeviceConfig.getDurationFromMillis(
+ DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
}
@Override
@NonNull
Duration getUncertaintyDelay() {
- return PROVIDER_UNCERTAINTY_DELAY;
+ return mDeviceConfig.getDurationFromMillis(
+ DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
+ DEFAULT_PROVIDER_UNCERTAINTY_DELAY);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 5bee7ee..364eaf8 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -43,6 +43,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.SystemService;
+import com.android.server.timedetector.DeviceConfig;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
import com.android.server.timezonedetector.TimeZoneDetectorService;
@@ -74,11 +75,17 @@
* mode" where the real binder clients are replaced by {@link
* SimulatedLocationTimeZoneProviderProxy}. This means that the real client providers are never
* bound (ensuring no real location events will be received) and simulated events / behaviors
- * can be injected via the command line. To enter simulation mode for a provider, use
- * "{@code adb shell setprop persist.sys.location_tz_simulation_mode.<provider name> 1}" and reboot.
- * e.g. "{@code adb shell setprop persist.sys.location_tz_simulation_mode.primary 1}}"
- * Then use "{@code adb shell cmd location_time_zone_manager help}" for injection. Set the system
- * properties to "0" and reboot to return to exit simulation mode.
+ * can be injected via the command line.
+ *
+ * <p>To enter simulation mode for a provider, use {@code adb shell cmd location_time_zone_manager
+ * set_provider_mode_override <provider name> simulated} and restart the service with {@code
+ * adb shell cmd location_time_zone_manager stop} and {@code adb shell cmd
+ * location_time_zone_manager start}.
+ *
+ * <p>e.g. {@code adb shell cmd location_time_zone_manager set_provider_mode_override primary
+ * simulated}.
+ *
+ * <p>See {@code adb shell cmd location_time_zone_manager help}" for more options.
*/
public class LocationTimeZoneManagerService extends Binder {
@@ -96,7 +103,7 @@
@Override
public void onStart() {
Context context = getContext();
- if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionEnabled(context)) {
+ if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) {
mService = new LocationTimeZoneManagerService(context);
// The service currently exposes no LocalService or Binder API, but it extends
@@ -110,7 +117,7 @@
@Override
public void onBootPhase(int phase) {
Context context = getContext();
- if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionEnabled(context)) {
+ if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
// The location service must be functioning after this boot phase.
mService.onSystemReady();
@@ -221,10 +228,10 @@
LocationTimeZoneProvider secondary = createSecondaryProvider();
mLocationTimeZoneDetectorController =
new ControllerImpl(mThreadingDomain, primary, secondary);
- ControllerCallbackImpl callback = new ControllerCallbackImpl(
- mThreadingDomain);
+ DeviceConfig deviceConfig = new DeviceConfig();
mEnvironment = new ControllerEnvironmentImpl(
- mThreadingDomain, mLocationTimeZoneDetectorController);
+ mThreadingDomain, deviceConfig, mLocationTimeZoneDetectorController);
+ ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
mLocationTimeZoneDetectorController.initialize(mEnvironment, callback);
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 38ae51f..83085cc 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -977,7 +977,7 @@
AudioPortConfig sourceConfig = mAudioSource.activeConfig();
List<AudioPortConfig> sinkConfigs = new ArrayList<>();
AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
- boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
+ boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated || mAudioPatch == null;
for (AudioDevicePort audioSink : mAudioSink) {
AudioPortConfig sinkConfig = audioSink.activeConfig();
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index 6427ae2..b6ddd93 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -18,16 +18,27 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.NetCapability;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.os.ParcelUuid;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* Tracks a set of Networks underpinning a VcnGatewayConnection.
@@ -38,53 +49,401 @@
*
* @hide
*/
-public class UnderlyingNetworkTracker extends Handler {
+public class UnderlyingNetworkTracker {
@NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
@NonNull private final VcnContext mVcnContext;
@NonNull private final ParcelUuid mSubscriptionGroup;
+ @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities;
@NonNull private final UnderlyingNetworkTrackerCallback mCb;
@NonNull private final Dependencies mDeps;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ConnectivityManager mConnectivityManager;
+
+ @NonNull private final Map<Integer, NetworkCallback> mCellBringupCallbacks = new ArrayMap<>();
+ @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback();
+ @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback();
+
+ @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
+ private boolean mIsRunning = true;
+
+ @Nullable private UnderlyingNetworkRecord mCurrentRecord;
+ @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
public UnderlyingNetworkTracker(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot snapshot,
+ @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
@NonNull UnderlyingNetworkTrackerCallback cb) {
- this(vcnContext, subscriptionGroup, cb, new Dependencies());
+ this(
+ vcnContext,
+ subscriptionGroup,
+ snapshot,
+ requiredUnderlyingNetworkCapabilities,
+ cb,
+ new Dependencies());
}
private UnderlyingNetworkTracker(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot snapshot,
+ @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
@NonNull UnderlyingNetworkTrackerCallback cb,
@NonNull Dependencies deps) {
- super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
- mVcnContext = vcnContext;
+ mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+ mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
+ mRequiredUnderlyingNetworkCapabilities =
+ Objects.requireNonNull(
+ requiredUnderlyingNetworkCapabilities,
+ "Missing requiredUnderlyingNetworkCapabilities");
mCb = Objects.requireNonNull(cb, "Missing cb");
mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+ mHandler = new Handler(mVcnContext.getLooper());
+
+ mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+
+ registerNetworkRequests();
+ }
+
+ private void registerNetworkRequests() {
+ // register bringup requests for underlying Networks
+ mConnectivityManager.requestBackgroundNetwork(
+ getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
+ updateSubIdsAndCellularRequests();
+
+ // register Network-selection request used to decide selected underlying Network
+ mConnectivityManager.requestBackgroundNetwork(
+ getNetworkRequestBase().build(), mHandler, mRouteSelectionCallback);
+ }
+
+ private NetworkRequest getWifiNetworkRequest() {
+ return getNetworkRequestBase().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
+ }
+
+ private NetworkRequest getCellNetworkRequestForSubId(int subId) {
+ return getNetworkRequestBase()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+ .build();
+ }
+
+ private NetworkRequest.Builder getNetworkRequestBase() {
+ NetworkRequest.Builder requestBase = new NetworkRequest.Builder();
+ for (@NetCapability int capability : mRequiredUnderlyingNetworkCapabilities) {
+ requestBase.addCapability(capability);
+ }
+
+ return requestBase
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
+
+ /**
+ * Update the current subIds and Cellular bringup requests for this UnderlyingNetworkTracker.
+ */
+ private void updateSubIdsAndCellularRequests() {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ // Don't bother re-filing NetworkRequests if this Tracker has been torn down.
+ if (!mIsRunning) {
+ return;
+ }
+
+ final Set<Integer> subIdsInSubGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
+
+ // new subIds to track = (updated list of subIds) - (currently tracked subIds)
+ final Set<Integer> subIdsToRegister = new ArraySet<>(subIdsInSubGroup);
+ subIdsToRegister.removeAll(mCellBringupCallbacks.keySet());
+
+ // subIds to stop tracking = (currently tracked subIds) - (updated list of subIds)
+ final Set<Integer> subIdsToUnregister = new ArraySet<>(mCellBringupCallbacks.keySet());
+ subIdsToUnregister.removeAll(subIdsInSubGroup);
+
+ for (final int subId : subIdsToRegister) {
+ final NetworkBringupCallback cb = new NetworkBringupCallback();
+ mCellBringupCallbacks.put(subId, cb);
+
+ mConnectivityManager.requestBackgroundNetwork(
+ getCellNetworkRequestForSubId(subId), mHandler, cb);
+ }
+
+ for (final int subId : subIdsToUnregister) {
+ final NetworkCallback cb = mCellBringupCallbacks.remove(subId);
+ mConnectivityManager.unregisterNetworkCallback(cb);
+ }
+ }
+
+ /**
+ * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot.
+ *
+ * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to
+ * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered
+ * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change.
+ */
+ public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+ Objects.requireNonNull(snapshot, "Missing snapshot");
+
+ mLastSnapshot = snapshot;
+ updateSubIdsAndCellularRequests();
}
/** Tears down this Tracker, and releases all underlying network requests. */
- public void teardown() {}
+ public void teardown() {
+ mVcnContext.ensureRunningOnLooperThread();
- /** An record of a single underlying network, caching relevant fields. */
+ mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback);
+ mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback);
+
+ for (final NetworkCallback cb : mCellBringupCallbacks.values()) {
+ mConnectivityManager.unregisterNetworkCallback(cb);
+ }
+ mCellBringupCallbacks.clear();
+
+ mIsRunning = false;
+ }
+
+ /** Returns whether the currently selected Network matches the given network. */
+ private static boolean isSameNetwork(
+ @Nullable UnderlyingNetworkRecord.Builder recordInProgress, @NonNull Network network) {
+ return recordInProgress != null && recordInProgress.getNetwork().equals(network);
+ }
+
+ /** Notify the Callback if a full UnderlyingNetworkRecord exists. */
+ private void maybeNotifyCallback() {
+ // Only forward this update if a complete record has been received
+ if (!mRecordInProgress.isValid()) {
+ return;
+ }
+
+ // Only forward this update if the updated record differs form the current record
+ UnderlyingNetworkRecord updatedRecord = mRecordInProgress.build();
+ if (!updatedRecord.equals(mCurrentRecord)) {
+ mCurrentRecord = updatedRecord;
+
+ mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+ }
+ }
+
+ private void handleNetworkAvailable(@NonNull Network network) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ mRecordInProgress = new UnderlyingNetworkRecord.Builder(network);
+ }
+
+ private void handleNetworkLost(@NonNull Network network) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Non-underlying Network lost");
+ return;
+ }
+
+ mRecordInProgress = null;
+ mCurrentRecord = null;
+ mCb.onSelectedUnderlyingNetworkChanged(null /* underlyingNetworkRecord */);
+ }
+
+ private void handleCapabilitiesChanged(
+ @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to NetworkCapabilities");
+ return;
+ }
+
+ mRecordInProgress.setNetworkCapabilities(networkCapabilities);
+
+ maybeNotifyCallback();
+ }
+
+ private void handleNetworkSuspended(@NonNull Network network, boolean isSuspended) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to isSuspended");
+ return;
+ }
+
+ final NetworkCapabilities newCaps =
+ new NetworkCapabilities(mRecordInProgress.getNetworkCapabilities());
+ if (isSuspended) {
+ newCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+ } else {
+ newCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+ }
+
+ handleCapabilitiesChanged(network, newCaps);
+ }
+
+ private void handlePropertiesChanged(
+ @NonNull Network network, @NonNull LinkProperties linkProperties) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to LinkProperties");
+ return;
+ }
+
+ mRecordInProgress.setLinkProperties(linkProperties);
+
+ maybeNotifyCallback();
+ }
+
+ private void handleNetworkBlocked(@NonNull Network network, boolean isBlocked) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to isBlocked");
+ return;
+ }
+
+ mRecordInProgress.setIsBlocked(isBlocked);
+
+ maybeNotifyCallback();
+ }
+
+ /**
+ * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
+ *
+ * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
+ * reaped, and no action is taken on any events firing.
+ */
+ @VisibleForTesting
+ class NetworkBringupCallback extends NetworkCallback {}
+
+ /**
+ * RouteSelectionCallback is used to select the "best" underlying Network.
+ *
+ * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
+ * truth.
+ */
+ @VisibleForTesting
+ class RouteSelectionCallback extends NetworkCallback {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ handleNetworkAvailable(network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ handleNetworkLost(network);
+ }
+
+ @Override
+ public void onCapabilitiesChanged(
+ @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+ handleCapabilitiesChanged(network, networkCapabilities);
+ }
+
+ @Override
+ public void onNetworkSuspended(@NonNull Network network) {
+ handleNetworkSuspended(network, true /* isSuspended */);
+ }
+
+ @Override
+ public void onNetworkResumed(@NonNull Network network) {
+ handleNetworkSuspended(network, false /* isSuspended */);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(
+ @NonNull Network network, @NonNull LinkProperties linkProperties) {
+ handlePropertiesChanged(network, linkProperties);
+ }
+
+ @Override
+ public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
+ handleNetworkBlocked(network, isBlocked);
+ }
+ }
+
+ /** A record of a single underlying network, caching relevant fields. */
public static class UnderlyingNetworkRecord {
@NonNull public final Network network;
@NonNull public final NetworkCapabilities networkCapabilities;
@NonNull public final LinkProperties linkProperties;
- public final boolean blocked;
+ public final boolean isBlocked;
@VisibleForTesting(visibility = Visibility.PRIVATE)
UnderlyingNetworkRecord(
@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
- boolean blocked) {
+ boolean isBlocked) {
this.network = network;
this.networkCapabilities = networkCapabilities;
this.linkProperties = linkProperties;
- this.blocked = blocked;
+ this.isBlocked = isBlocked;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UnderlyingNetworkRecord)) return false;
+ final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;
+
+ return network.equals(that.network)
+ && networkCapabilities.equals(that.networkCapabilities)
+ && linkProperties.equals(that.linkProperties)
+ && isBlocked == that.isBlocked;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
+ }
+
+ /** Builder to incrementally construct an UnderlyingNetworkRecord. */
+ private static class Builder {
+ @NonNull private final Network mNetwork;
+
+ @Nullable private NetworkCapabilities mNetworkCapabilities;
+ @Nullable private LinkProperties mLinkProperties;
+ boolean mIsBlocked;
+ boolean mWasIsBlockedSet;
+
+ private Builder(@NonNull Network network) {
+ mNetwork = network;
+ }
+
+ @NonNull
+ private Network getNetwork() {
+ return mNetwork;
+ }
+
+ private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ mNetworkCapabilities = networkCapabilities;
+ }
+
+ @Nullable
+ private NetworkCapabilities getNetworkCapabilities() {
+ return mNetworkCapabilities;
+ }
+
+ private void setLinkProperties(@NonNull LinkProperties linkProperties) {
+ mLinkProperties = linkProperties;
+ }
+
+ private void setIsBlocked(boolean isBlocked) {
+ mIsBlocked = isBlocked;
+ mWasIsBlockedSet = true;
+ }
+
+ private boolean isValid() {
+ return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
+ }
+
+ private UnderlyingNetworkRecord build() {
+ return new UnderlyingNetworkRecord(
+ mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
+ }
}
}
@@ -95,9 +454,10 @@
*
* <p>This callback does NOT signal a mobility event.
*
- * @param underlying The details of the new underlying network
+ * @param underlyingNetworkRecord The details of the new underlying network
*/
- void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying);
+ void onSelectedUnderlyingNetworkChanged(
+ @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
}
private static class Dependencies {}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 132883e..a82f239 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -27,9 +27,16 @@
import android.os.ParcelUuid;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* Represents an single instance of a VCN.
@@ -63,6 +70,15 @@
*/
private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
+ /**
+ * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed.
+ *
+ * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections.
+ *
+ * @param obj TelephonySubscriptionSnapshot
+ */
+ private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
+
/** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
@@ -76,20 +92,24 @@
new HashMap<>();
@NonNull private VcnConfig mConfig;
+ @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
private boolean mIsRunning = true;
public Vcn(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
- @NonNull VcnConfig config) {
- this(vcnContext, subscriptionGroup, config, new Dependencies());
+ @NonNull VcnConfig config,
+ @NonNull TelephonySubscriptionSnapshot snapshot) {
+ this(vcnContext, subscriptionGroup, config, snapshot, new Dependencies());
}
- private Vcn(
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public Vcn(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
@NonNull VcnConfig config,
+ @NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull Dependencies deps) {
super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
mVcnContext = vcnContext;
@@ -98,6 +118,7 @@
mRequestListener = new VcnNetworkRequestListener();
mConfig = Objects.requireNonNull(config, "Missing config");
+ mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
// Register to receive cached and future NetworkRequests
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
@@ -110,11 +131,24 @@
sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
}
+ /** Asynchronously updates the Subscription snapshot for this VCN. */
+ public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+ Objects.requireNonNull(snapshot, "Missing snapshot");
+
+ sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot));
+ }
+
/** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
public void teardownAsynchronously() {
sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
}
+ /** Get current Gateways for testing purposes */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public Set<VcnGatewayConnection> getVcnGatewayConnections() {
+ return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
+ }
+
private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
@Override
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
@@ -137,6 +171,9 @@
case MSG_EVENT_NETWORK_REQUESTED:
handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
break;
+ case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
+ handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
+ break;
case MSG_CMD_TEARDOWN:
handleTeardown();
break;
@@ -192,13 +229,26 @@
"Bringing up new VcnGatewayConnection for request " + request.requestId);
final VcnGatewayConnection vcnGatewayConnection =
- new VcnGatewayConnection(
- mVcnContext, mSubscriptionGroup, gatewayConnectionConfig);
+ mDeps.newVcnGatewayConnection(
+ mVcnContext,
+ mSubscriptionGroup,
+ mLastSnapshot,
+ gatewayConnectionConfig);
mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
}
}
}
+ private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
+ mLastSnapshot = snapshot;
+
+ if (mIsRunning) {
+ for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
+ gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
+ }
+ }
+ }
+
private boolean requestSatisfiedByGatewayConnectionConfig(
@NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
@@ -214,11 +264,24 @@
}
/** Retrieves the network score for a VCN Network */
- private int getNetworkScore() {
+ // Package visibility for use in VcnGatewayConnection
+ static int getNetworkScore() {
// TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in
// subGrp" value
return 52;
}
- private static class Dependencies {}
+ /** External dependencies used by Vcn, for injection in tests */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class Dependencies {
+ /** Builds a new VcnGatewayConnection */
+ public VcnGatewayConnection newVcnGatewayConnection(
+ VcnContext vcnContext,
+ ParcelUuid subscriptionGroup,
+ TelephonySubscriptionSnapshot snapshot,
+ VcnGatewayConnectionConfig connectionConfig) {
+ return new VcnGatewayConnection(
+ vcnContext, subscriptionGroup, snapshot, connectionConfig);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index dba59bd..7399e56 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -55,4 +55,15 @@
public VcnNetworkProvider getVcnNetworkProvider() {
return mVcnNetworkProvider;
}
+
+ /**
+ * Verifies that the caller is running on the VcnContext Thread.
+ *
+ * @throwsIllegalStateException if the caller is not running on the VcnContext Thread.
+ */
+ public void ensureRunningOnLooperThread() {
+ if (getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException("Not running on VcnMgmtSvc thread");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 3cfa00e..853bb43 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -17,8 +17,11 @@
package com.android.server.vcn;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static com.android.server.VcnManagementService.VDBG;
@@ -34,8 +37,10 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
+import android.net.TelephonyNetworkSpecifier;
import android.net.annotations.PolicyDirection;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -47,16 +52,21 @@
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnTransportInfo;
+import android.net.wifi.WifiInfo;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Message;
import android.os.ParcelUuid;
+import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
@@ -64,7 +74,9 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.util.Arrays;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -112,6 +124,9 @@
public class VcnGatewayConnection extends StateMachine {
private static final String TAG = VcnGatewayConnection.class.getSimpleName();
+ private static final int[] MERGED_CAPABILITIES =
+ new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
+
private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
@@ -358,6 +373,16 @@
*/
private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8;
+ /**
+ * Sent when this VcnGatewayConnection is notified of a change in TelephonySubscriptions.
+ *
+ * <p>Relevant in all states.
+ *
+ * @param arg1 The "all" token; this signal is always honored.
+ */
+ // TODO(b/178426520): implement handling of this event
+ private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
+
@VisibleForTesting(visibility = Visibility.PRIVATE)
@NonNull
final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -378,6 +403,11 @@
@NonNull
final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState();
+ @NonNull private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
+
@NonNull private final VcnContext mVcnContext;
@NonNull private final ParcelUuid mSubscriptionGroup;
@NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
@@ -456,14 +486,16 @@
public VcnGatewayConnection(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull VcnGatewayConnectionConfig connectionConfig) {
- this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies());
+ this(vcnContext, subscriptionGroup, snapshot, connectionConfig, new Dependencies());
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
VcnGatewayConnection(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
+ @NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull VcnGatewayConnectionConfig connectionConfig,
@NonNull Dependencies deps) {
super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
@@ -472,11 +504,19 @@
mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
mDeps = Objects.requireNonNull(deps, "Missing deps");
+ synchronized (mLock) {
+ mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
+ }
+
mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
mUnderlyingNetworkTracker =
mDeps.newUnderlyingNetworkTracker(
- mVcnContext, subscriptionGroup, mUnderlyingNetworkTrackerCallback);
+ mVcnContext,
+ subscriptionGroup,
+ mLastSnapshot,
+ mConnectionConfig.getAllUnderlyingCapabilities(),
+ mUnderlyingNetworkTrackerCallback);
mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
IpSecTunnelInterface iface;
@@ -529,10 +569,31 @@
mUnderlyingNetworkTracker.teardown();
}
+ /**
+ * Notify this Gateway that subscriptions have changed.
+ *
+ * <p>This snapshot should be used to update any keepalive requests necessary for potential
+ * underlying Networks in this Gateway's subscription group.
+ */
+ public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+ Objects.requireNonNull(snapshot, "Missing snapshot");
+
+ // Vcn is the only user of this method and runs on the same Thread, but lock around
+ // mLastSnapshot to be technically correct.
+ synchronized (mLock) {
+ mLastSnapshot = snapshot;
+ mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot);
+ }
+
+ sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
+ }
+
private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback {
@Override
public void onSelectedUnderlyingNetworkChanged(
@Nullable UnderlyingNetworkRecord underlying) {
+ // TODO(b/179091925): Move the delayed-message handling to BaseState
+
// If underlying is null, all underlying networks have been lost. Disconnect VCN after a
// timeout.
if (underlying == null) {
@@ -664,7 +725,8 @@
case EVENT_TRANSFORM_CREATED: // Fallthrough
case EVENT_SETUP_COMPLETED: // Fallthrough
case EVENT_DISCONNECT_REQUESTED: // Fallthrough
- case EVENT_TEARDOWN_TIMEOUT_EXPIRED:
+ case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough
+ case EVENT_SUBSCRIPTIONS_CHANGED:
logUnexpectedEvent(msg.what);
break;
default:
@@ -917,6 +979,8 @@
transitionTo(mDisconnectingState);
break;
case EVENT_SESSION_CLOSED:
+ // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
+ // message may not be posted again. Defer to ensure immediate shutdown.
deferMessage(msg);
transitionTo(mDisconnectingState);
@@ -937,7 +1001,108 @@
}
}
- private abstract class ConnectedStateBase extends ActiveBaseState {}
+ private abstract class ConnectedStateBase extends ActiveBaseState {
+ protected void updateNetworkAgent(
+ @NonNull IpSecTunnelInterface tunnelIface,
+ @NonNull NetworkAgent agent,
+ @NonNull ChildSessionConfiguration childConfig) {
+ final NetworkCapabilities caps =
+ buildNetworkCapabilities(mConnectionConfig, mUnderlying);
+ final LinkProperties lp =
+ buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
+
+ agent.sendNetworkCapabilities(caps);
+ agent.sendLinkProperties(lp);
+ }
+
+ protected NetworkAgent buildNetworkAgent(
+ @NonNull IpSecTunnelInterface tunnelIface,
+ @NonNull ChildSessionConfiguration childConfig) {
+ final NetworkCapabilities caps =
+ buildNetworkCapabilities(mConnectionConfig, mUnderlying);
+ final LinkProperties lp =
+ buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
+
+ final NetworkAgent agent =
+ new NetworkAgent(
+ mVcnContext.getContext(),
+ mVcnContext.getLooper(),
+ TAG,
+ caps,
+ lp,
+ Vcn.getNetworkScore(),
+ new NetworkAgentConfig(),
+ mVcnContext.getVcnNetworkProvider()) {
+ @Override
+ public void unwanted() {
+ teardownAsynchronously();
+ }
+ };
+
+ agent.register();
+ agent.markConnected();
+
+ return agent;
+ }
+
+ protected void applyTransform(
+ int token,
+ @NonNull IpSecTunnelInterface tunnelIface,
+ @NonNull Network underlyingNetwork,
+ @NonNull IpSecTransform transform,
+ int direction) {
+ try {
+ // TODO: Set underlying network of tunnel interface
+
+ // Transforms do not need to be persisted; the IkeSession will keep them alive
+ mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
+ } catch (IOException e) {
+ Slog.d(TAG, "Transform application failed for network " + token, e);
+ sessionLost(token, e);
+ }
+ }
+
+ protected void setupInterface(
+ int token,
+ @NonNull IpSecTunnelInterface tunnelIface,
+ @NonNull ChildSessionConfiguration childConfig) {
+ setupInterface(token, tunnelIface, childConfig, null);
+ }
+
+ protected void setupInterface(
+ int token,
+ @NonNull IpSecTunnelInterface tunnelIface,
+ @NonNull ChildSessionConfiguration childConfig,
+ @Nullable ChildSessionConfiguration oldChildConfig) {
+ try {
+ final Set<LinkAddress> newAddrs =
+ new ArraySet<>(childConfig.getInternalAddresses());
+ final Set<LinkAddress> existingAddrs = new ArraySet<>();
+ if (oldChildConfig != null) {
+ existingAddrs.addAll(oldChildConfig.getInternalAddresses());
+ }
+
+ final Set<LinkAddress> toAdd = new ArraySet<>();
+ toAdd.addAll(newAddrs);
+ toAdd.removeAll(existingAddrs);
+
+ final Set<LinkAddress> toRemove = new ArraySet<>();
+ toRemove.addAll(existingAddrs);
+ toRemove.removeAll(newAddrs);
+
+ for (LinkAddress address : toAdd) {
+ tunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+ }
+
+ for (LinkAddress address : toRemove) {
+ tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength());
+ }
+ } catch (IOException e) {
+ Slog.d(TAG, "Adding address to tunnel failed for token " + token, e);
+ sessionLost(token, e);
+ }
+ }
+ }
/**
* Stable state representing a VCN that has a functioning connection to the mobility anchor.
@@ -947,7 +1112,89 @@
*/
class ConnectedState extends ConnectedStateBase {
@Override
- protected void processStateMsg(Message msg) {}
+ protected void enterState() throws Exception {
+ // Successful connection, clear failed attempt counter
+ mFailedAttempts = 0;
+ }
+
+ @Override
+ protected void processStateMsg(Message msg) {
+ switch (msg.what) {
+ case EVENT_UNDERLYING_NETWORK_CHANGED:
+ handleUnderlyingNetworkChanged(msg);
+ break;
+ case EVENT_SESSION_CLOSED:
+ // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
+ // message may not be posted again. Defer to ensure immediate shutdown.
+ deferMessage(msg);
+ transitionTo(mDisconnectingState);
+ break;
+ case EVENT_SESSION_LOST:
+ transitionTo(mDisconnectingState);
+ break;
+ case EVENT_TRANSFORM_CREATED:
+ final EventTransformCreatedInfo transformCreatedInfo =
+ (EventTransformCreatedInfo) msg.obj;
+
+ applyTransform(
+ mCurrentToken,
+ mTunnelIface,
+ mUnderlying.network,
+ transformCreatedInfo.transform,
+ transformCreatedInfo.direction);
+ break;
+ case EVENT_SETUP_COMPLETED:
+ mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig;
+
+ setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig);
+ break;
+ case EVENT_DISCONNECT_REQUESTED:
+ handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+ break;
+ default:
+ logUnhandledMessage(msg);
+ break;
+ }
+ }
+
+ private void handleUnderlyingNetworkChanged(@NonNull Message msg) {
+ final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
+ mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+ if (mUnderlying == null) {
+ // Ignored for now; a new network may be coming up. If none does, the delayed
+ // NETWORK_LOST disconnect will be fired, and tear down the session + network.
+ return;
+ }
+
+ // mUnderlying assumed non-null, given check above.
+ // If network changed, migrate. Otherwise, update any existing networkAgent.
+ if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
+ mIkeSession.setNetwork(mUnderlying.network);
+ } else {
+ // oldUnderlying is non-null & underlying network itself has not changed
+ // (only network properties were changed).
+
+ // Network not yet set up, or child not yet connected.
+ if (mNetworkAgent != null && mChildConfig != null) {
+ // If only network properties changed and agent is active, update properties
+ updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig);
+ }
+ }
+ }
+
+ protected void setupInterfaceAndNetworkAgent(
+ int token,
+ @NonNull IpSecTunnelInterface tunnelIface,
+ @NonNull ChildSessionConfiguration childConfig) {
+ setupInterface(token, tunnelIface, childConfig);
+
+ if (mNetworkAgent == null) {
+ mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
+ } else {
+ updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig);
+ }
+ }
}
/**
@@ -962,7 +1209,8 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
static NetworkCapabilities buildNetworkCapabilities(
- @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) {
+ @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
+ @Nullable UnderlyingNetworkRecord underlying) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
builder.addTransportType(TRANSPORT_CELLULAR);
@@ -974,6 +1222,52 @@
builder.addCapability(cap);
}
+ if (underlying != null) {
+ final NetworkCapabilities underlyingCaps = underlying.networkCapabilities;
+
+ // Mirror merged capabilities.
+ for (int cap : MERGED_CAPABILITIES) {
+ if (underlyingCaps.hasCapability(cap)) {
+ builder.addCapability(cap);
+ }
+ }
+
+ // Set admin UIDs for ConnectivityDiagnostics use.
+ final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids();
+ Arrays.sort(underlyingAdminUids); // Sort to allow contains check below.
+
+ final int[] adminUids;
+ if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified
+ && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list.
+ underlyingAdminUids, underlyingCaps.getOwnerUid())) {
+ adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1);
+ adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid();
+ Arrays.sort(adminUids);
+ } else {
+ adminUids = underlyingAdminUids;
+ }
+ builder.setAdministratorUids(adminUids);
+
+ // Set TransportInfo for SysUI use (never parcelled out of SystemServer).
+ if (underlyingCaps.hasTransport(TRANSPORT_WIFI)
+ && underlyingCaps.getTransportInfo() instanceof WifiInfo) {
+ final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo();
+ builder.setTransportInfo(new VcnTransportInfo(wifiInfo));
+ } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR)
+ && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
+ final TelephonyNetworkSpecifier telNetSpecifier =
+ (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier();
+ builder.setTransportInfo(new VcnTransportInfo(telNetSpecifier.getSubscriptionId()));
+ } else {
+ Slog.wtf(
+ TAG,
+ "Unknown transport type or missing TransportInfo/NetworkSpecifier for"
+ + " non-null underlying network");
+ }
+ }
+
+ // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs.
+
return builder.build();
}
@@ -1134,8 +1428,15 @@
public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
VcnContext vcnContext,
ParcelUuid subscriptionGroup,
+ TelephonySubscriptionSnapshot snapshot,
+ Set<Integer> requiredUnderlyingNetworkCapabilities,
UnderlyingNetworkTrackerCallback callback) {
- return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
+ return new UnderlyingNetworkTracker(
+ vcnContext,
+ subscriptionGroup,
+ snapshot,
+ requiredUnderlyingNetworkCapabilities,
+ callback);
}
/** Builds a new IkeSession. */
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index b9babae..fe4ea30 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -25,6 +25,9 @@
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+
import java.util.Objects;
import java.util.Set;
@@ -52,8 +55,13 @@
super(context, looper, VcnNetworkProvider.class.getSimpleName());
}
- // Package-private
- void registerListener(@NonNull NetworkRequestListener listener) {
+ /**
+ * Registers a NetworkRequestListener with this NetworkProvider.
+ *
+ * <p>Upon registering, the provided listener will receive all cached requests.
+ */
+ @VisibleForTesting(visibility = Visibility.PACKAGE)
+ public void registerListener(@NonNull NetworkRequestListener listener) {
mListeners.add(listener);
// Send listener all cached requests
@@ -62,8 +70,9 @@
}
}
- // Package-private
- void unregisterListener(@NonNull NetworkRequestListener listener) {
+ /** Unregisters the specified listener from receiving future NetworkRequests. */
+ @VisibleForTesting(visibility = Visibility.PACKAGE)
+ public void unregisterListener(@NonNull NetworkRequestListener listener) {
mListeners.remove(listener);
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index fe3b03ab..e0f5408 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -138,6 +138,11 @@
return mStatus != Status.RUNNING;
}
+ /** Return true is effect is a repeating vibration. */
+ public boolean isRepeating() {
+ return mEffect.getDuration() == Long.MAX_VALUE;
+ }
+
/** Return the effect that should be played by this vibration. */
@Nullable
public CombinedVibrationEffect getEffect() {
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 536375f..8910bdf 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -34,10 +34,12 @@
import android.os.Vibrator;
import android.provider.Settings;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
+import com.android.server.VibratorServiceDumpProto;
import java.util.ArrayList;
import java.util.List;
@@ -340,6 +342,24 @@
+ '}';
}
+ /** Write current settings into given {@link ProtoOutputStream}. */
+ public void dumpProto(ProtoOutputStream proto) {
+ synchronized (mLock) {
+ proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+ mHapticFeedbackIntensity);
+ proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+ mVibrator.getDefaultHapticFeedbackIntensity());
+ proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+ mNotificationIntensity);
+ proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+ mVibrator.getDefaultNotificationVibrationIntensity());
+ proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+ mRingIntensity);
+ proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+ mVibrator.getDefaultRingVibrationIntensity());
+ }
+ }
+
private void notifyListeners() {
List<OnVibratorSettingsChanged> currentListeners;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 5355252..4f2fc86 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -114,12 +114,11 @@
}
}
- public Vibration getVibration() {
- return mVibration;
- }
-
@Override
public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Binder died, cancelling vibration...");
+ }
cancel();
}
@@ -150,12 +149,19 @@
/** Notify current vibration that a step has completed on given vibrator. */
public void vibratorComplete(int vibratorId) {
synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
+ }
if (mCurrentVibrateStep != null) {
mCurrentVibrateStep.vibratorComplete(vibratorId);
}
}
}
+ public Vibration getVibration() {
+ return mVibration;
+ }
+
@VisibleForTesting
SparseArray<VibratorController> getVibrators() {
return mVibrators;
@@ -467,7 +473,7 @@
noteVibratorOff();
}
if (DEBUG) {
- Slog.d(TAG, "SingleVibrateStep step done.");
+ Slog.d(TAG, "SingleVibrateStep done.");
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 5fe853a..ce951b8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -54,6 +54,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -100,6 +101,17 @@
}
@Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(
+ "ActivityClientController", e);
+ }
+ }
+
+ @Override
public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
final long origId = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4e359f2..36c5037 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
@@ -81,6 +82,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.EMPTY;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -201,9 +203,13 @@
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.ACTIVITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
@@ -249,6 +255,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -266,6 +273,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
+import android.permission.PermissionManager;
import android.service.dreams.DreamActivity;
import android.service.dreams.DreamManagerInternal;
import android.service.voice.IVoiceInteractionSession;
@@ -321,6 +329,7 @@
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.Task.ActivityState;
import com.android.server.wm.WindowManagerService.H;
+import com.android.server.wm.WindowManagerService.LetterboxBackgroundType;
import com.android.server.wm.utils.InsetUtils;
import com.google.android.collect.Sets;
@@ -859,6 +868,9 @@
pw.print(Integer.toHexString(taskDescription.getStatusBarColor()));
pw.print(" navigationBarColor=");
pw.println(Integer.toHexString(taskDescription.getNavigationBarColor()));
+ pw.print(" backgroundColorFloating=");
+ pw.println(Integer.toHexString(
+ taskDescription.getBackgroundColorFloating()));
}
}
if (results != null) {
@@ -1361,7 +1373,8 @@
if (mLetterbox == null) {
mLetterbox = new Letterbox(() -> makeChildSurface(null),
mWmService.mTransactionFactory,
- mWmService::isLetterboxActivityCornersRounded);
+ mWmService::isLetterboxActivityCornersRounded,
+ this::getLetterboxBackgroundColor);
mLetterbox.attachInput(w);
}
getPosition(mTmpPoint);
@@ -1381,6 +1394,31 @@
}
}
+ private Color getLetterboxBackgroundColor() {
+ final WindowState w = findMainWindow();
+ if (w == null || w.isLetterboxedForDisplayCutout()) {
+ return Color.valueOf(Color.BLACK);
+ }
+ @LetterboxBackgroundType int letterboxBackgroundType =
+ mWmService.getLetterboxBackgroundType();
+ switch (letterboxBackgroundType) {
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+ if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) {
+ return Color.valueOf(taskDescription.getBackgroundColorFloating());
+ }
+ return mWmService.getLetterboxBackgroundColor();
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+ if (taskDescription != null && taskDescription.getBackgroundColor() != 0) {
+ return Color.valueOf(taskDescription.getBackgroundColor());
+ }
+ // Falling through
+ case LETTERBOX_BACKGROUND_SOLID_COLOR:
+ return mWmService.getLetterboxBackgroundColor();
+ }
+ throw new AssertionError(
+ "Unexpected letterbox background type: " + letterboxBackgroundType);
+ }
+
/** @return {@code true} when main window is letterboxed and activity isn't transparent. */
private boolean isLetterboxed(WindowState mainWindow) {
return mainWindow.isLetterboxedAppWindow() && fillsParent();
@@ -6737,6 +6775,20 @@
// layout traversals.
mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
getResolvedOverrideConfiguration().seq = mConfigurationSeq;
+
+ // Sandbox max bounds by setting it to the app bounds, if activity is letterboxed or in
+ // size compat mode.
+ if (providesMaxBounds()) {
+ if (DEBUG_CONFIGURATION) {
+ ProtoLog.d(WM_DEBUG_CONFIGURATION, "Sandbox max bounds for uid %s to bounds %s "
+ + "due to letterboxing? %s mismatch with parent bounds? %s size compat "
+ + "mode %s", getUid(),
+ resolvedConfig.windowConfiguration.getBounds(), mLetterbox != null,
+ !matchParentBounds(), inSizeCompatMode());
+ }
+ resolvedConfig.windowConfiguration
+ .setMaxBounds(resolvedConfig.windowConfiguration.getBounds());
+ }
}
/**
@@ -6920,6 +6972,19 @@
return super.getBounds();
}
+ @Override
+ public boolean providesMaxBounds() {
+ // System and SystemUI should always be able to access the physical display bounds,
+ // so do not provide it with the overridden maximum bounds.
+ // TODO(b/179179513) check WindowState#mOwnerCanAddInternalSystemWindow instead
+ if (getUid() == SYSTEM_UID || PermissionManager.checkPermission(INTERNAL_SYSTEM_WINDOW,
+ getPid(), info.applicationInfo.uid) == PERMISSION_GRANTED) {
+ return false;
+ }
+ // Max bounds should be sandboxed when this is letterboxed or in size compat mode.
+ return mLetterbox != null || !matchParentBounds() || inSizeCompatMode();
+ }
+
@VisibleForTesting
@Override
Rect getAnimationBounds(int appRootTaskClipMode) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c6ed16c..d846c3a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2098,13 +2098,6 @@
final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity,
mLaunchFlags);
- // The above code can remove {@code reusedActivity} from the task, leading to the
- // {@code ActivityRecord} removing its reference to the {@code Task}. The task
- // reference is needed in the call below to {@link setTargetStackAndMoveToFrontIfNeeded}
- if (targetTaskTop.getTask() == null) {
- targetTask.addChild(targetTaskTop);
- }
-
if (top != null) {
if (top.isRootOfTask()) {
// Activity aliases may mean we use different intents for the top activity,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 64b1e042..2d6e9b2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2457,6 +2457,13 @@
if (referrer != null) {
pae.extras.putParcelable(Intent.EXTRA_REFERRER, referrer);
}
+ if (!pae.activity.isAttached()) {
+ // Skip directly because the caller activity may have been destroyed. If a caller
+ // is waiting for the assist data, it will be notified by timeout
+ // (see PendingAssistExtras#run()) and then pendingAssistExtrasTimedOut will clean
+ // up the request.
+ return;
+ }
if (structure != null) {
// Pre-fill the task/activity component for all assist data receivers
structure.setTaskId(pae.activity.getTask().mTaskId);
@@ -4842,15 +4849,21 @@
try {
return super.onTransact(code, data, reply, flags);
} catch (RuntimeException e) {
- if (!(e instanceof SecurityException)) {
- Slog.w(TAG, "Activity Task Manager onTransact aborts "
- + " UID:" + Binder.getCallingUid()
- + " PID:" + Binder.getCallingPid(), e);
- }
- throw e;
+ throw logAndRethrowRuntimeExceptionOnTransact(TAG, e);
}
}
+ /** Provides the full stack traces of non-security exception that occurs in onTransact. */
+ static RuntimeException logAndRethrowRuntimeExceptionOnTransact(String name,
+ RuntimeException e) {
+ if (!(e instanceof SecurityException)) {
+ Slog.w(TAG, name + " onTransact aborts"
+ + " UID:" + Binder.getCallingUid()
+ + " PID:" + Binder.getCallingPid(), e);
+ }
+ throw e;
+ }
+
/**
* Sets the corresponding {@link DisplayArea} information for the process global
* configuration. To be called when we need to show IME on a different {@link DisplayArea}
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
deleted file mode 100644
index 3c8cf4e..0000000
--- a/services/core/java/com/android/server/wm/BarController.java
+++ /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.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-
-/**
- * Controls state/behavior specific to a system bar window.
- */
-public class BarController {
- private final int mWindowType;
-
- private final Rect mContentFrame = new Rect();
-
- BarController(int windowType) {
- mWindowType = windowType;
- }
-
- /**
- * Sets the frame within which the bar will display its content.
- *
- * This is used to determine if letterboxes interfere with the display of such content.
- */
- void setContentFrame(Rect frame) {
- mContentFrame.set(frame);
- }
-
- private Rect getContentFrame(@NonNull WindowState win) {
- final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
- return rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
- }
-
- boolean isLightAppearanceAllowed(WindowState win) {
- if (win == null) {
- return true;
- }
- return !win.isLetterboxedOverlappingWith(getContentFrame(win));
- }
-
- /**
- * @return {@code true} if bar is allowed to be fully transparent when given window is show.
- *
- * <p>Prevents showing a transparent bar over a letterboxed activity which can make
- * notification icons or navigation buttons unreadable due to contrast between letterbox
- * background and an activity. For instance, this happens when letterbox background is solid
- * black while activity is white. To resolve this, only semi-transparent bars are allowed to
- * be drawn over letterboxed activity.
- */
- boolean isFullyTransparentAllowed(WindowState win) {
- if (win == null) {
- return true;
- }
- return win.isFullyTransparentBarAllowed(getContentFrame(win));
- }
-}
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index dbad8b3..a725dd3 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -24,6 +24,9 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.res.CompatibilityInfo;
@@ -32,6 +35,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -63,6 +67,58 @@
// Compatibility state: compatibility mode is enabled.
private static final int COMPAT_FLAG_ENABLED = 1<<1;
+ /**
+ * CompatModePackages#DOWNSCALED is the gatekeeper of all per-app buffer downscaling
+ * changes. Disabling this change will prevent the following scaling factors from working:
+ * CompatModePackages#DOWNSCALE_87_5
+ * CompatModePackages#DOWNSCALE_75
+ * CompatModePackages#DOWNSCALE_62_5
+ * CompatModePackages#DOWNSCALE_50
+ *
+ * If CompatModePackages#DOWNSCALED is enabled for an app package, then the app will be forcibly
+ * resized to the highest enabled scaling factor e.g. 87.5% if both 87.5% and 75% were
+ * enabled.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALED = 168419799L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_87_5 for a package will force the app to assume it's
+ * running on a display with 87.5% the vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_87_5 = 176926753L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_75 for a package will force the app to assume it's
+ * running on a display with 75% the vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_75 = 176926829L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_62_5 for a package will force the app to assume it's
+ * running on a display with 62.5% the vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_62_5 = 176926771L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_50 for a package will force the app to assume it's
+ * running on a display with 50% vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_50 = 176926741L;
+
private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
private static final int MSG_WRITE = 300;
@@ -191,11 +247,39 @@
mHandler.sendMessageDelayed(msg, 10000);
}
+ float getCompatScale(String packageName, int uid) {
+ if (!CompatChanges.isChangeEnabled(
+ DOWNSCALED, packageName, UserHandle.getUserHandleForUid(uid))) {
+ return 1f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_87_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+ // 8/7 == (1 / 0.875) ~= 1.14285714286
+ return 8f / 7f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_75, packageName, UserHandle.getUserHandleForUid(uid))) {
+ // 4/3 == (1 / 0.75) ~= 1.333333333
+ return 4f / 3f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_62_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+ // (1 / 0.625) == 1.6
+ return 1.6f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_50, packageName, UserHandle.getUserHandleForUid(uid))) {
+ return 2f;
+ }
+ return 1f;
+ }
+
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
final Configuration globalConfig = mService.getGlobalConfiguration();
+ final float requestedScale = getCompatScale(ai.packageName, ai.uid);
CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
globalConfig.smallestScreenWidthDp,
- (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
+ (getPackageFlags(ai.packageName) & COMPAT_FLAG_ENABLED) != 0, requestedScale);
//Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
return ci;
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 15483cb..f075d85 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -462,6 +462,23 @@
}
@Override
+ void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+ super.resolveOverrideConfiguration(newParentConfiguration);
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ final Rect overrideBounds = resolvedConfig.windowConfiguration.getBounds();
+ final Rect overrideAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+
+ // If there is no override of appBounds, restrict appBounds to the override bounds.
+ if (!overrideBounds.isEmpty() && (overrideAppBounds == null || overrideAppBounds.isEmpty())
+ && parentAppBounds != null && !parentAppBounds.isEmpty()) {
+ final Rect appBounds = new Rect(overrideBounds);
+ appBounds.intersect(parentAppBounds);
+ resolvedConfig.windowConfiguration.setAppBounds(appBounds);
+ }
+ }
+
+ @Override
boolean isOrganized() {
return mOrganizer != null;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 11cc2c6..3ab7952 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -204,6 +204,7 @@
import android.view.InsetsState.InternalInsetsType;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationDefinition;
+import android.view.RoundedCorners;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -337,6 +338,10 @@
= new RotationCache<>(this::calculateDisplayCutoutForRotationUncached);
boolean mIgnoreDisplayCutout;
+ RoundedCorners mInitialRoundedCorners;
+ private final RotationCache<RoundedCorners, RoundedCorners> mRoundedCornerCache =
+ new RotationCache<>(this::calculateRoundedCornersForRotationUncached);
+
/**
* Overridden display size. Initialized with {@link #mInitialDisplayWidth}
* and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
@@ -670,8 +675,9 @@
// Used in updating override configurations
private final Configuration mTempConfig = new Configuration();
- // Used in performing layout
- private boolean mTmpWindowsBehindIme;
+ // Used in performing layout, to record the insets provided by other windows above the current
+ // window.
+ private InsetsState mTmpAboveInsetsState = new InsetsState();
/**
* Used to prevent recursions when calling
@@ -770,17 +776,11 @@
+ " parentHidden=" + w.isParentWindowHidden());
}
- // Sets mBehindIme for each window. Windows behind IME can get IME insets.
- if (w.mBehindIme != mTmpWindowsBehindIme) {
- w.mBehindIme = mTmpWindowsBehindIme;
- if (getInsetsStateController().getRawInsetsState().getSourceOrDefaultVisibility(
- ITYPE_IME)) {
- // If IME is invisible, behind IME or not doesn't make the insets different.
- mWinInsetsChanged.add(w);
- }
- }
- if (w == mInputMethodWindow) {
- mTmpWindowsBehindIme = true;
+ // Sets mAboveInsets for each window. Windows behind the window providing the insets can
+ // receive the insets.
+ if (!w.mAboveInsetsState.equals(mTmpAboveInsetsState)) {
+ w.mAboveInsetsState.set(mTmpAboveInsetsState);
+ mWinInsetsChanged.add(w);
}
// If this view is GONE, then skip it -- keep the current frame, and let the caller know
@@ -816,8 +816,16 @@
+ " mContainingFrame=" + w.getContainingFrame()
+ " mDisplayFrame=" + w.getDisplayFrame());
}
+ provideInsetsByWindow(w);
};
+ private void provideInsetsByWindow(WindowState w) {
+ for (int i = 0; i < w.mProvidedInsetsSources.size(); i++) {
+ final InsetsSource providedSource = w.mProvidedInsetsSources.valueAt(i);
+ mTmpAboveInsetsState.addSource(providedSource);
+ }
+ }
+
private final Consumer<WindowState> mPerformLayoutAttached = w -> {
if (w.mLayoutAttached) {
if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
@@ -980,7 +988,8 @@
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mInsetsStateController = new InsetsStateController(this);
mDisplayFrames = new DisplayFrames(mDisplayId, mInsetsStateController.getRawInsetsState(),
- mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
+ mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
+ calculateRoundedCornersForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
@@ -1693,8 +1702,9 @@
mTmpConfiguration.unset();
final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
+ final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final DisplayFrames displayFrames = new DisplayFrames(mDisplayId, new InsetsState(), info,
- cutout);
+ cutout, roundedCorners);
token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
}
@@ -1955,6 +1965,23 @@
rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
}
+ RoundedCorners calculateRoundedCornersForRotation(int rotation) {
+ return mRoundedCornerCache.getOrCompute(mInitialRoundedCorners, rotation);
+ }
+
+ private RoundedCorners calculateRoundedCornersForRotationUncached(
+ RoundedCorners roundedCorners, int rotation) {
+ if (roundedCorners == null || roundedCorners == RoundedCorners.NO_ROUNDED_CORNERS) {
+ return RoundedCorners.NO_ROUNDED_CORNERS;
+ }
+
+ if (rotation == ROTATION_0) {
+ return roundedCorners;
+ }
+
+ return roundedCorners.rotate(rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+ }
+
/**
* Compute display info and configuration according to the given rotation without changing
* current display.
@@ -2482,7 +2509,8 @@
void onDisplayInfoChanged() {
final DisplayInfo info = mDisplayInfo;
- mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation));
+ mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation),
+ calculateRoundedCornersForRotation(info.rotation));
mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
mDisplayPolicy.onDisplayInfoChanged(info);
}
@@ -2515,6 +2543,7 @@
mInitialDisplayHeight = mDisplayInfo.logicalHeight;
mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
mInitialDisplayCutout = mDisplayInfo.displayCutout;
+ mInitialRoundedCorners = mDisplayInfo.roundedCorners;
}
/**
@@ -2532,11 +2561,13 @@
final DisplayCutout newCutout = mIgnoreDisplayCutout
? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout;
final String newUniqueId = mDisplayInfo.uniqueId;
+ final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners;
final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
|| mInitialDisplayHeight != newHeight
|| mInitialDisplayDensity != mDisplayInfo.logicalDensityDpi
- || !Objects.equals(mInitialDisplayCutout, newCutout);
+ || !Objects.equals(mInitialDisplayCutout, newCutout)
+ || !Objects.equals(mInitialRoundedCorners, newRoundedCorners);
final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId);
if (displayMetricsChanged || physicalDisplayChanged) {
@@ -2555,6 +2586,7 @@
mInitialDisplayHeight = newHeight;
mInitialDisplayDensity = newDensity;
mInitialDisplayCutout = newCutout;
+ mInitialRoundedCorners = newRoundedCorners;
mCurrentUniqueDisplayId = newUniqueId;
reconfigureDisplayLocked();
}
@@ -4283,14 +4315,20 @@
+ " dh=" + mDisplayInfo.logicalHeight);
}
+ // Used to indicate that we have processed the insets windows. This needs to be after
+ // beginLayoutLw to ensure the raw insets state display related info is initialized.
+ final InsetsState rawInsetsState = getInsetsStateController().getRawInsetsState();
+ mTmpAboveInsetsState = new InsetsState();
+ mTmpAboveInsetsState.setDisplayFrame(rawInsetsState.getDisplayFrame());
+ mTmpAboveInsetsState.setDisplayCutout(rawInsetsState.getDisplayCutout());
+ mTmpAboveInsetsState.mirrorAlwaysVisibleInsetsSources(rawInsetsState);
+
int seq = mLayoutSeq + 1;
if (seq < 0) seq = 0;
mLayoutSeq = seq;
mTmpInitial = initial;
- // Used to indicate that we have processed the IME window.
- mTmpWindowsBehindIme = false;
// First perform layout of any root windows (not attached to another window).
forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
@@ -5491,12 +5529,9 @@
// The callback is only interested in the foreground changes of fullscreen activity.
return;
}
+ // TODO(b/178327644) Update for per Task size compat
if (!r.inSizeCompatMode()) {
if (mLastCompatModeActivity != null) {
- // TODO(b/178327644) Remove notifySizeCompatModeActivityChanged
- mAtmService.getTaskChangeNotificationController()
- .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
- // This will do nothing until SizeCompatModeActivityController is moved to shell
organizedTask.onSizeCompatActivityChanged();
}
mLastCompatModeActivity = null;
@@ -5506,10 +5541,6 @@
return;
}
mLastCompatModeActivity = r;
- // TODO(b/178327644) Remove notifySizeCompatModeActivityChanged
- mAtmService.getTaskChangeNotificationController()
- .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
- // This will do nothing until SizeCompatModeActivityController is moved to shell
organizedTask.onSizeCompatActivityChanged();
}
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 43c1435..e4230a2 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -21,12 +21,12 @@
import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
-import android.annotation.NonNull;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.InsetsState;
+import android.view.RoundedCorners;
import com.android.server.wm.utils.WmDisplayCutout;
@@ -47,9 +47,6 @@
*/
public final Rect mUnrestricted = new Rect();
- /** The display cutout used for layout (after rotation) */
- @NonNull public WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT;
-
/**
* During layout, the frame that is display-cutout safe, i.e. that does not intersect with it.
*/
@@ -61,26 +58,37 @@
public int mRotation;
public DisplayFrames(int displayId, InsetsState insetsState, DisplayInfo info,
- WmDisplayCutout displayCutout) {
+ WmDisplayCutout displayCutout, RoundedCorners roundedCorners) {
mDisplayId = displayId;
mInsetsState = insetsState;
- onDisplayInfoUpdated(info, displayCutout);
+ onDisplayInfoUpdated(info, displayCutout, roundedCorners);
}
- public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout) {
+ /**
+ * Update {@link DisplayFrames} when {@link DisplayInfo} is updated.
+ *
+ * @param info the updated {@link DisplayInfo}.
+ * @param displayCutout the updated {@link DisplayCutout}.
+ * @param roundedCorners the updated {@link RoundedCorners}.
+ */
+ public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout,
+ RoundedCorners roundedCorners) {
mDisplayWidth = info.logicalWidth;
mDisplayHeight = info.logicalHeight;
mRotation = info.rotation;
- mDisplayCutout = displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
+ final WmDisplayCutout wmDisplayCutout =
+ displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
final InsetsState state = mInsetsState;
final Rect unrestricted = mUnrestricted;
final Rect safe = mDisplayCutoutSafe;
- final DisplayCutout cutout = mDisplayCutout.getDisplayCutout();
+ final DisplayCutout cutout = wmDisplayCutout.getDisplayCutout();
unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
safe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
state.setDisplayFrame(unrestricted);
state.setDisplayCutout(cutout);
+ state.setRoundedCorners(roundedCorners != null ? roundedCorners
+ : RoundedCorners.NO_ROUNDED_CORNERS);
if (!cutout.isEmpty()) {
if (cutout.getSafeInsetLeft() > 0) {
safe.left = unrestricted.left + cutout.getSafeInsetLeft();
@@ -118,12 +126,5 @@
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
+ " r=" + mRotation);
- final String myPrefix = prefix + " ";
- dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
- pw.println(myPrefix + "mDisplayCutout=" + mDisplayCutout);
- }
-
- private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
- pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f52cb09..02e281f5 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -37,6 +37,7 @@
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+import static android.view.ViewRootImpl.computeWindowBounds;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -86,6 +87,7 @@
import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
@@ -127,7 +129,6 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.util.ArraySet;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -141,8 +142,6 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
-import android.view.WindowInsets.Side;
-import android.view.WindowInsets.Side.InsetsSide;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
@@ -167,7 +166,6 @@
import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition;
import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
-import com.android.server.policy.WindowOrientationListener;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
import com.android.server.wm.InputMonitor.EventReceiverInputConsumer;
@@ -305,8 +303,7 @@
private boolean mLastImmersiveMode;
- private final BarController mStatusBarController;
- private final BarController mNavigationBarController;
+ private final SparseArray<Rect> mBarContentFrames = new SparseArray<>();
// The windows we were told about in focusChanged.
private WindowState mFocusedWindow;
@@ -432,8 +429,8 @@
final int displayId = displayContent.getDisplayId();
- mStatusBarController = new BarController(TYPE_STATUS_BAR);
- mNavigationBarController = new BarController(TYPE_NAVIGATION_BAR);
+ mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
+ mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
final Resources r = mContext.getResources();
mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -1253,11 +1250,6 @@
displayFrames.mDisplayCutoutSafe.top);
}
- @VisibleForTesting
- BarController getStatusBarController() {
- return mStatusBarController;
- }
-
WindowState getStatusBar() {
return mStatusBar != null ? mStatusBar : mStatusBarAlt;
}
@@ -1401,29 +1393,15 @@
*
* @param attrs The LayoutParams of the window.
* @param windowToken The token of the window.
- * @param outFrame The frame of the window.
* @param outInsetsState The insets state of this display from the client's perspective.
* @param localClient Whether the client is from the our process.
* @return Whether to always consume the system bars.
* See {@link #areSystemBarsForcedShownLw(WindowState)}.
*/
- boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, Rect outFrame,
- InsetsState outInsetsState, boolean localClient) {
- final boolean isFixedRotationTransforming =
- windowToken != null && windowToken.isFixedRotationTransforming();
- final ActivityRecord activity = windowToken != null ? windowToken.asActivityRecord() : null;
- final Task task = activity != null ? activity.getTask() : null;
- final Rect taskBounds = isFixedRotationTransforming
- // Use token (activity) bounds if it is rotated because its task is not rotated.
- ? windowToken.getBounds()
- : (task != null ? task.getBounds() : null);
+ boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, InsetsState outInsetsState,
+ boolean localClient) {
final InsetsState state =
mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
- computeWindowBounds(attrs, state, windowToken, outFrame);
- if (taskBounds != null) {
- outFrame.intersect(taskBounds);
- }
-
final boolean inSizeCompatMode = WindowState.inSizeCompatMode(attrs, windowToken);
outInsetsState.set(state, inSizeCompatMode || localClient);
if (inSizeCompatMode) {
@@ -1473,7 +1451,7 @@
mSystemGestures.screenHeight = info.logicalHeight;
}
- private void layoutStatusBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+ private void layoutStatusBar(DisplayFrames displayFrames, Rect contentFrame) {
// decide where the status bar goes ahead of time
if (mStatusBar == null) {
return;
@@ -1500,21 +1478,16 @@
statusBarBottom);
}
- // Tell the bar controller where the collapsed status bar content is.
sTmpRect.set(windowFrames.mFrame);
sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
sTmpRect.top = windowFrames.mFrame.top; // Ignore top display cutout inset
sTmpRect.bottom = statusBarBottom; // Use collapsed status bar size
- if (simulatedContentFrame != null) {
- simulatedContentFrame.set(sTmpRect);
- } else {
- mStatusBarController.setContentFrame(sTmpRect);
- }
+ contentFrame.set(sTmpRect);
}
- private void layoutNavigationBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+ private int layoutNavigationBar(DisplayFrames displayFrames, Rect contentFrame) {
if (mNavigationBar == null) {
- return;
+ return NAV_BAR_INVALID;
}
final int uiMode = mDisplayContent.getConfiguration().uiMode;
@@ -1552,17 +1525,12 @@
windowFrames.setFrames(navigationFrame /* parentFrame */,
navigationFrame /* displayFrame */);
mNavigationBar.computeFrameAndUpdateSourceFrame();
- final Rect contentFrame = sTmpRect;
- contentFrame.set(windowFrames.mFrame);
- contentFrame.intersect(displayFrames.mDisplayCutoutSafe);
- if (simulatedContentFrame != null) {
- simulatedContentFrame.set(contentFrame);
- } else {
- mNavigationBarPosition = navBarPosition;
- mNavigationBarController.setContentFrame(contentFrame);
- }
+ sTmpRect.set(windowFrames.mFrame);
+ sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+ contentFrame.set(sTmpRect);
if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
+ return navBarPosition;
}
private boolean canReceiveInput(WindowState win) {
@@ -1574,28 +1542,6 @@
return !notFocusableForIm;
}
- private void computeWindowBounds(WindowManager.LayoutParams attrs, InsetsState state,
- @Nullable WindowToken windowToken, Rect outBounds) {
- final @InsetsType int typesToFit = attrs.getFitInsetsTypes();
- final @InsetsSide int sidesToFit = attrs.getFitInsetsSides();
- final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
- final Rect df = windowToken != null ? windowToken.getBounds() : state.getDisplayFrame();
- Insets insets = Insets.of(0, 0, 0, 0);
- for (int i = types.size() - 1; i >= 0; i--) {
- final InsetsSource source = state.peekSource(types.valueAt(i));
- if (source == null) {
- continue;
- }
- insets = Insets.max(insets, source.calculateInsets(
- df, attrs.isFitInsetsIgnoringVisibility()));
- }
- final int left = (sidesToFit & Side.LEFT) != 0 ? insets.left : 0;
- final int top = (sidesToFit & Side.TOP) != 0 ? insets.top : 0;
- final int right = (sidesToFit & Side.RIGHT) != 0 ? insets.right : 0;
- final int bottom = (sidesToFit & Side.BOTTOM) != 0 ? insets.bottom : 0;
- outBounds.set(df.left + left, df.top + top, df.right - right, df.bottom - bottom);
- }
-
/**
* Called for each window attached to the window manager as layout is proceeding. The
* implementation of this function must take care of setting the window's frame, either here or
@@ -1609,11 +1555,12 @@
*/
public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
if (win == mNavigationBar) {
- layoutNavigationBar(displayFrames, null /* simulatedContentFrame */);
+ mNavigationBarPosition = layoutNavigationBar(displayFrames,
+ mBarContentFrames.get(TYPE_NAVIGATION_BAR));
return;
}
if ((win == mStatusBar && !canReceiveInput(win))) {
- layoutStatusBar(displayFrames, null /* simulatedContentFrame */);
+ layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
return;
}
final WindowManager.LayoutParams attrs = win.getAttrs();
@@ -1635,7 +1582,7 @@
final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
final InsetsState state = win.getInsetsState();
- computeWindowBounds(attrs, state, win.mToken, df);
+ computeWindowBounds(attrs, state, win.mToken.getBounds(), df);
if (attached == null) {
pf.set(df);
if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
@@ -2622,7 +2569,7 @@
// Otherwise if it's dimming, clear the light flag.
appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
}
- if (!mStatusBarController.isLightAppearanceAllowed(statusColorWin)) {
+ if (!isLightBarAllowed(statusColorWin, TYPE_STATUS_BAR)) {
appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
}
}
@@ -2685,7 +2632,7 @@
// Clear the light flag for dimming window.
appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
}
- if (!mNavigationBarController.isLightAppearanceAllowed(navColorWin)) {
+ if (!isLightBarAllowed(navColorWin, TYPE_NAVIGATION_BAR)) {
appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
}
}
@@ -2739,7 +2686,36 @@
return appearance;
}
- private boolean drawsBarBackground(WindowState win, BarController controller) {
+ private boolean isLightBarAllowed(WindowState win, int windowType) {
+ if (win == null) {
+ return true;
+ }
+ return !win.isLetterboxedOverlappingWith(getBarContentFrameForWindow(win, windowType));
+ }
+
+ private Rect getBarContentFrameForWindow(WindowState win, int windowType) {
+ final Rect rotatedBarFrame = win.mToken.getFixedRotationBarContentFrame(windowType);
+ return rotatedBarFrame != null ? rotatedBarFrame : mBarContentFrames.get(windowType);
+ }
+
+ /**
+ * @return {@code true} if bar is allowed to be fully transparent when given window is show.
+ *
+ * <p>Prevents showing a transparent bar over a letterboxed activity which can make
+ * notification icons or navigation buttons unreadable due to contrast between letterbox
+ * background and an activity. For instance, this happens when letterbox background is solid
+ * black while activity is white. To resolve this, only semi-transparent bars are allowed to
+ * be drawn over letterboxed activity.
+ */
+ @VisibleForTesting
+ boolean isFullyTransparentAllowed(WindowState win, int windowType) {
+ if (win == null) {
+ return true;
+ }
+ return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, windowType));
+ }
+
+ private boolean drawsBarBackground(WindowState win) {
if (win == null) {
return true;
}
@@ -2752,27 +2728,19 @@
return forceDrawsSystemBars || drawsSystemBars;
}
- private boolean drawsStatusBarBackground(WindowState win) {
- return drawsBarBackground(win, mStatusBarController);
- }
-
- private boolean drawsNavigationBarBackground(WindowState win) {
- return drawsBarBackground(win, mNavigationBarController);
- }
-
/** @return the current visibility flags with the status bar opacity related flags toggled. */
private int configureStatusBarOpacity(int appearance) {
final boolean fullscreenDrawsBackground =
- drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
+ drawsBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsBackground =
- drawsStatusBarBackground(mTopDockedOpaqueWindowState);
+ drawsBarBackground(mTopDockedOpaqueWindowState);
if (fullscreenDrawsBackground && dockedDrawsBackground) {
appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
}
- if (!mStatusBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
- || !mStatusBarController.isFullyTransparentAllowed(mTopDockedOpaqueWindowState)) {
+ if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_STATUS_BAR)
+ || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_STATUS_BAR)) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
}
@@ -2788,9 +2756,9 @@
final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
final boolean fullscreenDrawsBackground =
- drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
+ drawsBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsBackground =
- drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
+ drawsBarBackground(mTopDockedOpaqueWindowState);
if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
if (fullscreenDrawsBackground && dockedDrawsBackground) {
@@ -2818,9 +2786,8 @@
}
}
- if (!mNavigationBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
- || !mNavigationBarController.isFullyTransparentAllowed(
- mTopDockedOpaqueWindowState)) {
+ if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_NAVIGATION_BAR)
+ || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_NAVIGATION_BAR)) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6a86aee..48e4df7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -69,7 +69,6 @@
import com.android.server.LocalServices;
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.policy.WindowOrientationListener;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
@@ -253,7 +252,7 @@
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
- mOrientationListener = new OrientationListener(mContext, uiHandler);
+ mOrientationListener = new OrientationListener(mContext, uiHandler, mService);
mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
@@ -1474,8 +1473,8 @@
final SparseArray<Runnable> mRunnableCache = new SparseArray<>(5);
boolean mEnabled;
- OrientationListener(Context context, Handler handler) {
- super(context, handler);
+ OrientationListener(Context context, Handler handler, WindowManagerService service) {
+ super(context, handler, service);
}
private class UpdateRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1692df6..c6c7fe0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -151,6 +151,7 @@
// animate-out as new one animates-in.
mWin.cancelAnimation();
mWin.mPendingPositionChanged = null;
+ mWin.mProvidedInsetsSources.remove(mSource.getType());
}
ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
mWin = win;
@@ -160,11 +161,14 @@
setServerVisible(false);
mSource.setFrame(new Rect());
mSource.setVisibleFrame(null);
- } else if (mControllable) {
- mWin.setControllableInsetProvider(this);
- if (mPendingControlTarget != null) {
- updateControlForTarget(mPendingControlTarget, true /* force */);
- mPendingControlTarget = null;
+ } else {
+ mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);
+ if (mControllable) {
+ mWin.setControllableInsetProvider(this);
+ if (mPendingControlTarget != null) {
+ updateControlForTarget(mPendingControlTarget, true /* force */);
+ mPendingControlTarget = null;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 267f677..580d328 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
@@ -104,6 +105,8 @@
* visible to the target. e.g., the source which represents the target window itself, and the
* IME source when the target is above IME. We also need to exclude certain types of insets
* source for client within specific windowing modes.
+ * This is to get the insets for a window layout on the screen. If the window is not there, use
+ * the {@link #getInsetsForWindowMetrics} to get insets instead.
*
* @param target The window associate with the perspective.
* @return The state stripped of the necessary information.
@@ -117,8 +120,8 @@
final @InternalInsetsType int type = provider != null
? provider.getSource().getType() : ITYPE_INVALID;
return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(),
- isAboveIme(target),
- target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : mState);
+ target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() :
+ target.mAboveInsetsState);
}
InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
@@ -133,19 +136,7 @@
final @WindowingMode int windowingMode = token != null
? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
- return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token), mState);
- }
-
- private boolean isAboveIme(WindowContainer target) {
- final WindowState imeWindow = mDisplayContent.mInputMethodWindow;
- if (target == null || imeWindow == null) {
- return false;
- }
- if (target instanceof WindowState) {
- final WindowState win = (WindowState) target;
- return win.needsRelativeLayeringToIme() || !win.mBehindIme;
- }
- return false;
+ return getInsetsForTarget(type, windowingMode, alwaysOnTop, mState);
}
private static @InternalInsetsType
@@ -181,10 +172,12 @@
* @see #getInsetsForWindowMetrics
*/
private InsetsState getInsetsForTarget(@InternalInsetsType int type,
- @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme,
- @NonNull InsetsState state) {
+ @WindowingMode int windowingMode, boolean isAlwaysOnTop, InsetsState state) {
+ boolean stateCopied = false;
+
if (type != ITYPE_INVALID) {
state = new InsetsState(state);
+ stateCopied = true;
state.removeSource(type);
// Navigation bar doesn't get influenced by anything else
@@ -219,20 +212,15 @@
if (WindowConfiguration.isFloating(windowingMode)
|| (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
- state = new InsetsState(state);
+ if (!stateCopied) {
+ state = new InsetsState(state);
+ stateCopied = true;
+ }
state.removeSource(ITYPE_STATUS_BAR);
state.removeSource(ITYPE_NAVIGATION_BAR);
state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
- }
-
- if (aboveIme) {
- InsetsSource imeSource = state.peekSource(ITYPE_IME);
- if (imeSource != null && imeSource.isVisible()) {
- imeSource = new InsetsSource(imeSource);
- imeSource.setVisible(false);
- imeSource.setFrame(0, 0, 0, 0);
- state = new InsetsState(state);
- state.addSource(imeSource);
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ state.removeSource(ITYPE_IME);
}
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 02a43b7..2274a4a 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -19,6 +19,7 @@
import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.SurfaceControl.HIDDEN;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -45,6 +46,8 @@
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
private final Supplier<Boolean> mAreCornersRounded;
+ private final Supplier<Color> mColorSupplier;
+
private final Rect mOuter = new Rect();
private final Rect mInner = new Rect();
private final LetterboxSurface mTop = new LetterboxSurface("top");
@@ -64,10 +67,12 @@
*/
public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- Supplier<Boolean> areCornersRounded) {
+ Supplier<Boolean> areCornersRounded,
+ Supplier<Color> colorSupplier) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAreCornersRounded = areCornersRounded;
+ mColorSupplier = colorSupplier;
}
/**
@@ -268,6 +273,7 @@
private final String mType;
private SurfaceControl mSurface;
+ private Color mColor;
private final Rect mSurfaceFrameRelative = new Rect();
private final Rect mLayoutFrameGlobal = new Rect();
@@ -292,9 +298,8 @@
.setColorLayer()
.setCallsite("LetterboxSurface.createSurface")
.build();
- t.setLayer(mSurface, -1)
- .setColor(mSurface, new float[]{0, 0, 0})
- .setColorSpaceAgnostic(mSurface, true);
+
+ t.setLayer(mSurface, -1).setColorSpaceAgnostic(mSurface, true);
}
void attachInput(WindowState win) {
@@ -344,6 +349,14 @@
if (mSurface == null) {
createSurface(t);
}
+
+ mColor = mColorSupplier.get();
+ final float[] rgbTmpFloat = new float[3];
+ rgbTmpFloat[0] = mColor.red();
+ rgbTmpFloat[1] = mColor.green();
+ rgbTmpFloat[2] = mColor.blue();
+ t.setColor(mSurface, rgbTmpFloat);
+
t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
mSurfaceFrameRelative.height());
@@ -358,7 +371,8 @@
}
public boolean needsApplySurfaceChanges() {
- return !mSurfaceFrameRelative.equals(mLayoutFrameRelative);
+ return !mSurfaceFrameRelative.equals(mLayoutFrameRelative)
+ || mColorSupplier.get() != mColor;
}
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index e1fdefd..e02cce4 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -421,7 +421,10 @@
if (skipAnimation(task)) {
continue;
}
- addAnimation(task, !recentTaskIds.get(task.mTaskId));
+ addAnimation(task, !recentTaskIds.get(task.mTaskId), false /* hidden */,
+ (type, anim) -> task.forAllWindows(win -> {
+ win.onAnimationFinished(type, anim);
+ }, true /* traverseTopToBottom */));
}
// Skip the animation if there is nothing to animate
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bbf6c76..61fec0d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -37,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -846,6 +847,8 @@
Slog.i(TAG,
">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
}
+ // Send any pending task-info changes that were queued-up during a layout deferment
+ mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
mWmService.openSurfaceTransaction();
try {
@@ -862,8 +865,6 @@
}
}
- // Send any pending task-info changes that were queued-up during a layout deferment
- mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
checkAppTransitionReady(surfacePlacer);
@@ -2180,6 +2181,8 @@
// display area, so reparent.
rootTask.reparent(taskDisplayArea, true /* onTop */);
}
+ mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CHANGE, rootTask);
+
// Defer the windowing mode change until after the transition to prevent the activity
// from doing work and changing the activity visuals while animating
// TODO(task-org): Figure-out more structured way to do this long term.
diff --git a/services/core/java/com/android/server/wm/ImpressionAttestationController.java b/services/core/java/com/android/server/wm/ScreenshotHashController.java
similarity index 79%
rename from services/core/java/com/android/server/wm/ImpressionAttestationController.java
rename to services/core/java/com/android/server/wm/ScreenshotHashController.java
index bad6c80..03f4e28 100644
--- a/services/core/java/com/android/server/wm/ImpressionAttestationController.java
+++ b/services/core/java/com/android/server/wm/ScreenshotHashController.java
@@ -16,7 +16,9 @@
package com.android.server.wm;
-import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+import static android.service.screenshot.ScreenshotHasherService.EXTRA_SCREENSHOT_HASH;
+import static android.service.screenshot.ScreenshotHasherService.EXTRA_VERIFICATION_STATUS;
+import static android.service.screenshot.ScreenshotHasherService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -44,9 +46,9 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.service.attestation.IImpressionAttestationService;
-import android.service.attestation.ImpressionAttestationService;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.IScreenshotHasherService;
+import android.service.screenshot.ScreenshotHash;
+import android.service.screenshot.ScreenshotHasherService;
import android.util.Slog;
import android.view.MagnificationSpec;
@@ -59,30 +61,29 @@
import java.util.function.BiConsumer;
/**
- * Handles requests into {@link ImpressionAttestationService}
+ * Handles requests into {@link android.service.screenshot.ScreenshotHasherService}
*
* Do not hold the {@link WindowManagerService#mGlobalLock} when calling methods since they are
* blocking calls into another service.
*/
-public class ImpressionAttestationController {
- private static final String TAG =
- TAG_WITH_CLASS_NAME ? "ImpressionAttestationController" : TAG_WM;
+public class ScreenshotHashController {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenshotHashController" : TAG_WM;
private static final boolean DEBUG = false;
private final Object mServiceConnectionLock = new Object();
@GuardedBy("mServiceConnectionLock")
- private ImpressionAttestationServiceConnection mServiceConnection;
+ private ScreenshotHasherServiceConnection mServiceConnection;
private final Context mContext;
/**
- * Lock used for the cached {@link #mImpressionAlgorithms} array
+ * Lock used for the cached {@link #mHashingAlgorithms} array
*/
- private final Object mImpressionAlgorithmsLock = new Object();
+ private final Object mHashingAlgorithmsLock = new Object();
- @GuardedBy("mImpressionAlgorithmsLock")
- private String[] mImpressionAlgorithms;
+ @GuardedBy("mHashingAlgorithmsLock")
+ private String[] mHashingAlgorithms;
private final Handler mHandler;
@@ -93,24 +94,24 @@
private final RectF mTmpRectF = new RectF();
private interface Command {
- void run(IImpressionAttestationService service) throws RemoteException;
+ void run(IScreenshotHasherService service) throws RemoteException;
}
- ImpressionAttestationController(Context context) {
+ ScreenshotHashController(Context context) {
mContext = context;
mHandler = new Handler(Looper.getMainLooper());
mSalt = UUID.randomUUID().toString().getBytes();
}
- String[] getSupportedImpressionAlgorithms() {
- // We have a separate lock for the impression algorithm array since it doesn't need to make
+ String[] getSupportedHashingAlgorithms() {
+ // We have a separate lock for the hashing algorithm array since it doesn't need to make
// the request through the service connection. Instead, we have a lock to ensure we can
- // properly cache the impression algorithms array so we don't need to call into the
+ // properly cache the hashing algorithms array so we don't need to call into the
// ExtServices process for each request.
- synchronized (mImpressionAlgorithmsLock) {
+ synchronized (mHashingAlgorithmsLock) {
// Already have cached values
- if (mImpressionAlgorithms != null) {
- return mImpressionAlgorithms;
+ if (mHashingAlgorithms != null) {
+ return mHashingAlgorithms;
}
final ServiceInfo serviceInfo = getServiceInfo();
@@ -127,54 +128,55 @@
final int resourceId = serviceInfo.metaData.getInt(
SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
- mImpressionAlgorithms = res.getStringArray(resourceId);
+ mHashingAlgorithms = res.getStringArray(resourceId);
- return mImpressionAlgorithms;
+ return mHashingAlgorithms;
}
}
- boolean verifyImpressionToken(ImpressionToken impressionToken) {
+ boolean verifyScreenshotHash(ScreenshotHash screenshotHash) {
final SyncCommand syncCommand = new SyncCommand();
Bundle results = syncCommand.run((service, remoteCallback) -> {
try {
- service.verifyImpressionToken(mSalt, impressionToken, remoteCallback);
+ service.verifyScreenshotHash(mSalt, screenshotHash, remoteCallback);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to invoke verifyImpressionToken command");
+ Slog.e(TAG, "Failed to invoke verifyScreenshotHash command");
}
});
- return results.getBoolean(ImpressionAttestationService.EXTRA_VERIFICATION_STATUS);
+ return results.getBoolean(EXTRA_VERIFICATION_STATUS);
}
- ImpressionToken generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+ ScreenshotHash generateScreenshotHash(HardwareBuffer screenshot, Rect bounds,
String hashAlgorithm) {
final SyncCommand syncCommand = new SyncCommand();
Bundle results = syncCommand.run((service, remoteCallback) -> {
try {
- service.generateImpressionToken(mSalt, screenshot, bounds, hashAlgorithm,
+ service.generateScreenshotHash(mSalt, screenshot, bounds, hashAlgorithm,
remoteCallback);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to invoke generateImpressionToken command", e);
+ Slog.e(TAG, "Failed to invoke generateScreenshotHash command", e);
}
});
- return results.getParcelable(ImpressionAttestationService.EXTRA_IMPRESSION_TOKEN);
+ return results.getParcelable(EXTRA_SCREENSHOT_HASH);
}
/**
- * Calculate the bounds to take the screenshot when generating the impression token. This takes
- * into account window transform, magnification, and display bounds.
+ * Calculate the bounds to take the screenshot when generating the ScreenshotHash. This
+ * takes into account window transform, magnification, and display bounds.
*
* Call while holding {@link WindowManagerService#mGlobalLock}
*
- * @param win Window that the impression token is generated for.
+ * @param win Window that the ScreenshotHash is generated for.
* @param boundsInWindow The bounds passed in about where in the window to take the screenshot.
* @param outBounds The result of the calculated bounds
*/
- void calculateImpressionTokenBoundsLocked(WindowState win, Rect boundsInWindow,
+ void calculateScreenshotHashBoundsLocked(WindowState win, Rect boundsInWindow,
Rect outBounds) {
if (DEBUG) {
- Slog.d(TAG, "calculateImpressionTokenBounds: boundsInWindow=" + boundsInWindow);
+ Slog.d(TAG,
+ "calculateScreenshotHashBoundsLocked: boundsInWindow=" + boundsInWindow);
}
outBounds.set(boundsInWindow);
@@ -192,7 +194,8 @@
outBounds.intersectUnchecked(windowBounds);
if (DEBUG) {
- Slog.d(TAG, "calculateImpressionTokenBounds: boundsIntersectWindow=" + outBounds);
+ Slog.d(TAG,
+ "calculateScreenshotHashBoundsLocked: boundsIntersectWindow=" + outBounds);
}
if (outBounds.isEmpty()) {
@@ -207,7 +210,7 @@
outBounds.set((int) mTmpRectF.left, (int) mTmpRectF.top, (int) mTmpRectF.right,
(int) mTmpRectF.bottom);
if (DEBUG) {
- Slog.d(TAG, "calculateImpressionTokenBounds: boundsInDisplay=" + outBounds);
+ Slog.d(TAG, "calculateScreenshotHashBoundsLocked: boundsInDisplay=" + outBounds);
}
// Apply the magnification spec values to the bounds since the content could be magnified
@@ -218,7 +221,8 @@
}
if (DEBUG) {
- Slog.d(TAG, "calculateImpressionTokenBounds: boundsWithMagnification=" + outBounds);
+ Slog.d(TAG, "calculateScreenshotHashBoundsLocked: boundsWithMagnification="
+ + outBounds);
}
if (outBounds.isEmpty()) {
@@ -230,7 +234,7 @@
final Rect displayBounds = displayContent.getBounds();
outBounds.intersectUnchecked(displayBounds);
if (DEBUG) {
- Slog.d(TAG, "calculateImpressionTokenBounds: finalBounds=" + outBounds);
+ Slog.d(TAG, "calculateScreenshotHashBoundsLocked: finalBounds=" + outBounds);
}
}
@@ -244,7 +248,7 @@
if (DEBUG) Slog.v(TAG, "creating connection");
// Create the connection
- mServiceConnection = new ImpressionAttestationServiceConnection();
+ mServiceConnection = new ScreenshotHasherServiceConnection();
final ComponentName component = getServiceComponentName();
if (DEBUG) Slog.v(TAG, "binding to: " + component);
@@ -275,7 +279,7 @@
return null;
}
- final Intent intent = new Intent(ImpressionAttestationService.SERVICE_INTERFACE);
+ final Intent intent = new Intent(ScreenshotHasherService.SERVICE_INTERFACE);
intent.setPackage(packageName);
final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
@@ -292,10 +296,10 @@
if (serviceInfo == null) return null;
final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
- if (!Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE
+ if (!Manifest.permission.BIND_SCREENSHOT_HASHER_SERVICE
.equals(serviceInfo.permission)) {
Slog.w(TAG, name.flattenToShortString() + " requires permission "
- + Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE);
+ + Manifest.permission.BIND_SCREENSHOT_HASHER_SERVICE);
return null;
}
@@ -308,7 +312,7 @@
private Bundle mResult;
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
- public Bundle run(BiConsumer<IImpressionAttestationService, RemoteCallback> func) {
+ public Bundle run(BiConsumer<IScreenshotHasherService, RemoteCallback> func) {
connectAndRun(service -> {
RemoteCallback callback = new RemoteCallback(result -> {
mResult = result;
@@ -327,9 +331,9 @@
}
}
- private class ImpressionAttestationServiceConnection implements ServiceConnection {
+ private class ScreenshotHasherServiceConnection implements ServiceConnection {
@GuardedBy("mServiceConnectionLock")
- private IImpressionAttestationService mRemoteService;
+ private IScreenshotHasherService mRemoteService;
@GuardedBy("mServiceConnectionLock")
private ArrayList<Command> mQueuedCommands;
@@ -338,7 +342,7 @@
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) Slog.v(TAG, "onServiceConnected(): " + name);
synchronized (mServiceConnectionLock) {
- mRemoteService = IImpressionAttestationService.Stub.asInterface(service);
+ mRemoteService = IScreenshotHasherService.Stub.asInterface(service);
if (mQueuedCommands != null) {
final int size = mQueuedCommands.size();
if (DEBUG) Slog.d(TAG, "running " + size + " queued commands");
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 3d3e31d..1f8daf6 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -22,7 +22,6 @@
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
-import static android.Manifest.permission.USE_BACKGROUND_BLUR;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
@@ -58,7 +57,7 @@
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.MergedConfiguration;
@@ -111,7 +110,6 @@
final boolean mCanCreateSystemApplicationOverlay;
final boolean mCanHideNonSystemOverlayWindows;
final boolean mCanAcquireSleepToken;
- final boolean mCanUseBackgroundBlur;
private AlertWindowNotification mAlertWindowNotification;
private boolean mShowingAlertWindowNotificationAllowed;
private boolean mClientDead = false;
@@ -142,8 +140,6 @@
&& !mService.mAtmInternal.isCallerRecents(mUid);
mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
== PERMISSION_GRANTED;
- mCanUseBackgroundBlur = service.mContext.checkCallingOrSelfPermission(USE_BACKGROUND_BLUR)
- == PERMISSION_GRANTED;
mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
mDragDropController = mService.mDragDropController;
StringBuilder sb = new StringBuilder();
@@ -196,32 +192,30 @@
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsState requestedVisibility, Rect outFrame,
+ int viewVisibility, int displayId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), requestedVisibility, outFrame, outInputChannel,
- outInsetsState, outActiveControls);
+ UserHandle.getUserId(mUid), requestedVisibility, outInputChannel, outInsetsState,
+ outActiveControls);
}
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
- Rect outFrame, InputChannel outInputChannel, InsetsState outInsetsState,
+ InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
- requestedVisibility, outFrame, outInputChannel, outInsetsState,
- outActiveControls);
+ requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
}
@Override
public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsState outInsetsState) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), mDummyRequestedVisibility,
- new Rect() /* outFrame */, null /* outInputChannel */, outInsetsState,
- mDummyControls);
+ UserHandle.getUserId(mUid), mDummyRequestedVisibility, null /* outInputChannel */,
+ outInsetsState, mDummyControls);
}
@Override
@@ -866,11 +860,11 @@
}
@Override
- public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+ public ScreenshotHash generateScreenshotHash(IWindow window, Rect boundsInWindow,
String hashAlgorithm) {
final long origId = Binder.clearCallingIdentity();
try {
- return mService.generateImpressionToken(this, window, boundsInWindow, hashAlgorithm);
+ return mService.generateScreenshotHash(this, window, boundsInWindow, hashAlgorithm);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f3f608b..57ba915 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -77,6 +77,7 @@
import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -144,6 +145,7 @@
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.TASK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -2090,7 +2092,9 @@
td.setEnsureNavigationBarContrastWhenTransparent(
atd.getEnsureNavigationBarContrastWhenTransparent());
}
-
+ if (td.getBackgroundColorFloating() == 0) {
+ td.setBackgroundColorFloating(atd.getBackgroundColorFloating());
+ }
}
// End search once we get to root.
@@ -2878,6 +2882,16 @@
// In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
outBounds.setEmpty();
computeLetterboxBounds(outBounds, newParentConfig);
+ // Since the task is letterboxed due to mismatched orientation against its parent,
+ // sandbox max bounds to the app bounds.
+ if (!outBounds.isEmpty()) {
+ if (DEBUG_CONFIGURATION) {
+ ProtoLog.d(WM_DEBUG_CONFIGURATION, "Sandbox max bounds due to mismatched "
+ + "orientation with parent, to %s vs DisplayArea %s", outBounds,
+ getDisplayArea() != null ? getDisplayArea().getBounds() : "null");
+ }
+ getResolvedOverrideConfiguration().windowConfiguration.setMaxBounds(outBounds);
+ }
}
/** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 3bd11ba..e18219e 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -23,7 +23,6 @@
import android.content.ComponentName;
import android.os.Binder;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
@@ -52,15 +51,14 @@
private static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG = 18;
private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG = 19;
- private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
- private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
- private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 22;
- private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 23;
- private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 24;
- private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 25;
- private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 26;
- private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 27;
- private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 28;
+ private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 20;
+ private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 21;
+ private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 22;
+ private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 23;
+ private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 24;
+ private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 25;
+ private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 26;
+ private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 27;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -151,10 +149,6 @@
l.onTaskSnapshotChanged(m.arg1, (TaskSnapshot) m.obj);
};
- private final TaskStackConsumer mOnSizeCompatModeActivityChanged = (l, m) -> {
- l.onSizeCompatModeActivityChanged(m.arg1, (IBinder) m.obj);
- };
-
private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> {
l.onTaskDisplayChanged(m.arg1, m.arg2);
};
@@ -250,9 +244,6 @@
case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg);
break;
- case NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG:
- forAllRemoteListeners(mOnSizeCompatModeActivityChanged, msg);
- break;
case NOTIFY_BACK_PRESSED_ON_TASK_ROOT:
forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg);
break;
@@ -490,17 +481,6 @@
}
/**
- * Notify listeners that whether a size compatibility mode activity is using the override
- * bounds which is not fit its parent.
- */
- void notifySizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
- final Message msg = mHandler.obtainMessage(NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG,
- displayId, 0 /* unused */, activityToken);
- forAllLocalListeners(mOnSizeCompatModeActivityChanged, msg);
- msg.sendToTarget();
- }
-
- /**
* Notify listeners that an activity received a back press when there are no other activities
* in the back stack.
*/
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index b3e0108..9fac3f0 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS;
import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
@@ -33,6 +34,7 @@
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.util.Slog;
import android.view.SurfaceControl;
@@ -357,8 +359,14 @@
mGlobalLock = atm.mGlobalLock;
}
- private void enforceTaskPermission(String func) {
- mService.enforceTaskPermission(func);
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(TAG, e);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 09df71c..07610ab 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -226,9 +226,8 @@
}
int displayId = activity.getDisplayContent().getDisplayId();
try {
- final int res = session.addToDisplay(window, layoutParams,
- View.GONE, displayId, mTmpInsetsState, tmpFrames.frame,
- null /* outInputChannel */, mTmpInsetsState, mTempControls);
+ final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
+ mTmpInsetsState, null /* outInputChannel */, mTmpInsetsState, mTempControls);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
return null;
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index a5ebf9a..015a0fb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -49,6 +49,10 @@
static final String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
"system_gesture_exclusion_log_debounce_millis";
+ // Enable logging from the sensor which publishes accel and gyro data generating a rotation
+ // event
+ private static final String KEY_RAW_SENSOR_LOGGING_ENABLED = "raw_sensor_logging_enabled";
+
private static final int MIN_GESTURE_EXCLUSION_LIMIT_DP = 200;
/** @see #KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS */
@@ -58,6 +62,8 @@
/** @see AndroidDeviceConfig#KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE */
boolean mSystemGestureExcludedByPreQStickyImmersive;
+ boolean mRawSensorLoggingEnabled;
+
private final WindowManagerGlobalLock mGlobalLock;
private final Runnable mUpdateSystemGestureExclusionCallback;
private final DeviceConfigInterface mDeviceConfig;
@@ -133,6 +139,9 @@
case KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS:
updateSystemGestureExclusionLogDebounceMillis();
break;
+ case KEY_RAW_SENSOR_LOGGING_ENABLED:
+ updateRawSensorDataLoggingEnabled();
+ break;
default:
break;
}
@@ -158,6 +167,12 @@
KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
}
+ private void updateRawSensorDataLoggingEnabled() {
+ mRawSensorLoggingEnabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_RAW_SENSOR_LOGGING_ENABLED, false);
+ }
+
void dump(PrintWriter pw) {
pw.println("WINDOW MANAGER CONSTANTS (dumpsys window constants):");
@@ -167,6 +182,8 @@
pw.print("="); pw.println(mSystemGestureExclusionLimitDp);
pw.print(" "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
+ pw.print(" "); pw.print(KEY_RAW_SENSOR_LOGGING_ENABLED);
+ pw.print("="); pw.println(mRawSensorLoggingEnabled);
pw.println();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 931f529..673c6a5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -160,6 +160,7 @@
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
@@ -201,7 +202,7 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.sysprop.SurfaceFlingerProperties;
@@ -457,7 +458,8 @@
*/
static final float MIN_TASK_LETTERBOX_ASPECT_RATIO = 1.0f;
- final WindowManagerConstants mConstants;
+ @VisibleForTesting
+ WindowManagerConstants mConstants;
final WindowTracing mWindowTracing;
@@ -767,7 +769,7 @@
final EmbeddedWindowController mEmbeddedWindowController;
final AnrController mAnrController;
- private final ImpressionAttestationController mImpressionAttestationController;
+ private final ScreenshotHashController mScreenshotHashController;
private final WindowContextListenerController mWindowContextListenerController =
new WindowContextListenerController();
@@ -1010,10 +1012,30 @@
// Aspect ratio of task level letterboxing, values <= MIN_TASK_LETTERBOX_ASPECT_RATIO will be
// ignored.
- private float mTaskLetterboxAspectRatio;
+ private volatile float mTaskLetterboxAspectRatio;
+
+ /** Enum for Letterbox background type. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
+ LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING})
+ @interface LetterboxBackgroundType {};
+ /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */
+ static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0;
+
+ /** Color specified in R.attr.colorBackground for the letterboxed application. */
+ static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND = 1;
+
+ /** Color specified in R.attr.colorBackgroundFloating for the letterboxed application. */
+ static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING = 2;
// Corners radius for activities presented in the letterbox mode, values < 0 will be ignored.
- private int mLetterboxActivityCornersRadius;
+ private volatile int mLetterboxActivityCornersRadius;
+
+ // Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type.
+ private volatile Color mLetterboxBackgroundColor;
+
+ @LetterboxBackgroundType
+ private volatile int mLetterboxBackgroundType;
final InputManagerService mInputManager;
final DisplayManagerInternal mDisplayManagerInternal;
@@ -1240,10 +1262,15 @@
com.android.internal.R.bool.config_perDisplayFocusEnabled);
mAssistantOnTopOfDream = context.getResources().getBoolean(
com.android.internal.R.bool.config_assistantOnTopOfDream);
+
mTaskLetterboxAspectRatio = context.getResources().getFloat(
com.android.internal.R.dimen.config_taskLetterboxAspectRatio);
mLetterboxActivityCornersRadius = context.getResources().getInteger(
com.android.internal.R.integer.config_letterboxActivityCornersRadius);
+ mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor(
+ com.android.internal.R.color.config_letterboxBackgroundColor));
+ mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context);
+
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1392,7 +1419,7 @@
mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources(
mContext.getResources());
- mImpressionAttestationController = new ImpressionAttestationController(mContext);
+ mScreenshotHashController = new ScreenshotHashController(mContext);
setGlobalShadowSettings();
mAnrController = new AnrController(this);
mStartingSurfaceController = new StartingSurfaceController(this);
@@ -1461,7 +1488,7 @@
}
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
- int displayId, int requestUserId, InsetsState requestedVisibility, Rect outFrame,
+ int displayId, int requestUserId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
Arrays.fill(outActiveControls, null);
@@ -1652,7 +1679,7 @@
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
- session.mCanAddInternalSystemWindow, session.mCanUseBackgroundBlur);
+ session.mCanAddInternalSystemWindow);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
@@ -1803,7 +1830,7 @@
prepareNoneTransitionForRelaunching(activity);
}
- if (displayPolicy.getLayoutHint(win.mAttrs, token, outFrame, outInsetsState,
+ if (displayPolicy.getLayoutHint(win.mAttrs, token, outInsetsState,
win.isClientLocal())) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
}
@@ -2568,11 +2595,12 @@
} else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
focusMayChange = true;
win.mAnimatingExit = true;
- } else if (win.isAnimating(TRANSITION | PARENTS)) {
+ } else if (win.mDisplayContent.okToAnimate() && win.isAnimating(TRANSITION | PARENTS)) {
// Currently in a hide animation... turn this into
// an exit.
win.mAnimatingExit = true;
- } else if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) {
+ } else if (win.mDisplayContent.okToAnimate()
+ && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) {
// If the wallpaper is currently behind this
// window, we need to change both of them inside
// of a transaction to avoid artifacts.
@@ -3901,14 +3929,7 @@
* the framework implementation will be used to determine the aspect ratio.
*/
void setTaskLetterboxAspectRatio(float aspectRatio) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- mTaskLetterboxAspectRatio = aspectRatio;
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ mTaskLetterboxAspectRatio = aspectRatio;
}
/**
@@ -3916,29 +3937,15 @@
* com.android.internal.R.dimen.config_taskLetterboxAspectRatio}.
*/
void resetTaskLetterboxAspectRatio() {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- mTaskLetterboxAspectRatio = mContext.getResources().getFloat(
- com.android.internal.R.dimen.config_taskLetterboxAspectRatio);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ mTaskLetterboxAspectRatio = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_taskLetterboxAspectRatio);
}
/**
* Gets the aspect ratio of task level letterboxing.
*/
float getTaskLetterboxAspectRatio() {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- return mTaskLetterboxAspectRatio;
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ return mTaskLetterboxAspectRatio;
}
/**
@@ -3948,14 +3955,7 @@
* and corners of the activity won't be rounded.
*/
void setLetterboxActivityCornersRadius(int cornersRadius) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- mLetterboxActivityCornersRadius = cornersRadius;
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ mLetterboxActivityCornersRadius = cornersRadius;
}
/**
@@ -3963,15 +3963,8 @@
* com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
*/
void resetLetterboxActivityCornersRadius() {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_letterboxActivityCornersRadius);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_letterboxActivityCornersRadius);
}
/**
@@ -3985,14 +3978,67 @@
* Gets corners raidus for activities presented in the letterbox mode.
*/
int getLetterboxActivityCornersRadius() {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- return mLetterboxActivityCornersRadius;
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ return mLetterboxActivityCornersRadius;
+ }
+
+ /**
+ * Gets color of letterbox background which is used when {@link
+ * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+ * fallback for other backfround types.
+ */
+ Color getLetterboxBackgroundColor() {
+ return mLetterboxBackgroundColor;
+ }
+
+
+ /**
+ * Sets color of letterbox background which is used when {@link
+ * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+ * fallback for other backfround types.
+ */
+ void setLetterboxBackgroundColor(Color color) {
+ mLetterboxBackgroundColor = color;
+ }
+
+ /**
+ * Resets color of letterbox background to {@link
+ * com.android.internal.R.color.config_letterboxBackgroundColor}.
+ */
+ void resetLetterboxBackgroundColor() {
+ mLetterboxBackgroundColor = Color.valueOf(mContext.getResources().getColor(
+ com.android.internal.R.color.config_letterboxBackgroundColor));
+ }
+
+ /**
+ * Gets {@link LetterboxBackgroundType} specified in {@link
+ * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
+ */
+ @LetterboxBackgroundType
+ int getLetterboxBackgroundType() {
+ return mLetterboxBackgroundType;
+ }
+
+ /** Sets letterbox background type. */
+ void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
+ mLetterboxBackgroundType = backgroundType;
+ }
+
+ /**
+ * Resets cletterbox background type to {@link
+ * com.android.internal.R.integer.config_letterboxBackgroundType}.
+ */
+ void resetLetterboxBackgroundType() {
+ mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+ }
+
+ @LetterboxBackgroundType
+ private static int readLetterboxBackgroundTypeFromConfig(Context context) {
+ int backgroundType = context.getResources().getInteger(
+ com.android.internal.R.integer.config_letterboxBackgroundType);
+ return backgroundType == LETTERBOX_BACKGROUND_SOLID_COLOR
+ || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND
+ || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING
+ ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR;
}
@Override
@@ -8510,8 +8556,8 @@
+ "could not be found!");
}
final WindowToken windowToken = dc.getWindowToken(attrs.token);
- return dc.getDisplayPolicy().getLayoutHint(attrs, windowToken,
- mTmpRect /* outFrame */, outInsetsState, fromLocal);
+ return dc.getDisplayPolicy().getLayoutHint(attrs, windowToken, outInsetsState,
+ fromLocal);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -8616,38 +8662,38 @@
}
@Override
- public String[] getSupportedImpressionAlgorithms() {
- return mImpressionAttestationController.getSupportedImpressionAlgorithms();
+ public String[] getSupportedScreenshotHashingAlgorithms() {
+ return mScreenshotHashController.getSupportedHashingAlgorithms();
}
@Override
- public boolean verifyImpressionToken(ImpressionToken impressionToken) {
- return mImpressionAttestationController.verifyImpressionToken(impressionToken);
+ public boolean verifyScreenshotHash(ScreenshotHash screenshotHash) {
+ return mScreenshotHashController.verifyScreenshotHash(screenshotHash);
}
- ImpressionToken generateImpressionToken(Session session, IWindow window,
+ ScreenshotHash generateScreenshotHash(Session session, IWindow window,
Rect boundsInWindow, String hashAlgorithm) {
final SurfaceControl displaySurfaceControl;
final Rect boundsInDisplay = new Rect(boundsInWindow);
synchronized (mGlobalLock) {
final WindowState win = windowForClientLocked(session, window, false);
if (win == null) {
- Slog.w(TAG, "Failed to generate impression token. Invalid window");
+ Slog.w(TAG, "Failed to generate ScreenshotHash. Invalid window");
return null;
}
DisplayContent displayContent = win.getDisplayContent();
if (displayContent == null) {
- Slog.w(TAG, "Failed to generate impression token. Window is not on a display");
+ Slog.w(TAG, "Failed to generate ScreenshotHash. Window is not on a display");
return null;
}
displaySurfaceControl = displayContent.getSurfaceControl();
- mImpressionAttestationController.calculateImpressionTokenBoundsLocked(win,
+ mScreenshotHashController.calculateScreenshotHashBoundsLocked(win,
boundsInWindow, boundsInDisplay);
if (boundsInDisplay.isEmpty()) {
- Slog.w(TAG, "Failed to generate impression token. Bounds are not on screen");
+ Slog.w(TAG, "Failed to generate ScreenshotHash. Bounds are not on screen");
return null;
}
}
@@ -8667,11 +8713,11 @@
SurfaceControl.captureLayers(args);
if (screenshotHardwareBuffer == null
|| screenshotHardwareBuffer.getHardwareBuffer() == null) {
- Slog.w(TAG, "Failed to generate impression token. Failed to take screenshot");
+ Slog.w(TAG, "Failed to generate ScreenshotHash. Failed to take screenshot");
return null;
}
- return mImpressionAttestationController.generateImpressionToken(
+ return mScreenshotHashController.generateScreenshotHash(
screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow, hashAlgorithm);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index badd29a..645786c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -18,6 +18,11 @@
import static android.os.Build.IS_USER;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR;
+
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
@@ -34,6 +39,7 @@
import com.android.internal.protolog.ProtoLogImpl;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerService.LetterboxBackgroundType;
import java.io.IOException;
import java.io.PrintWriter;
@@ -119,6 +125,14 @@
return runSetLetterboxActivityCornersRadius(pw);
case "get-letterbox-activity-corners-radius":
return runGetLetterboxActivityCornersRadius(pw);
+ case "set-letterbox-background-type":
+ return runSetLetterboxBackgroundType(pw);
+ case "get-letterbox-background-type":
+ return runGetLetterboxBackgroundType(pw);
+ case "set-letterbox-background-color":
+ return runSetLetterboxBackgroundColor(pw);
+ case "get-letterbox-background-color":
+ return runGetLetterboxBackgroundColor(pw);
case "reset":
return runReset(pw);
default:
@@ -581,6 +595,79 @@
return 0;
}
+ private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
+ @LetterboxBackgroundType final int backgroundType;
+
+ String arg = getNextArgRequired();
+ if ("reset".equals(arg)) {
+ mInternal.resetLetterboxBackgroundType();
+ return 0;
+ }
+ switch (arg) {
+ case "solid_color":
+ backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR;
+ break;
+ case "app_color_background":
+ backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+ break;
+ case "app_color_background_floating":
+ backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'reset', 'solid_color' or 'app_color_background' should "
+ + "be provided as an argument");
+ return -1;
+ }
+
+ mInternal.setLetterboxBackgroundType(backgroundType);
+ return 0;
+ }
+
+ private int runGetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
+ @LetterboxBackgroundType final int backgroundType = mInternal.getLetterboxBackgroundType();
+ switch (backgroundType) {
+ case LETTERBOX_BACKGROUND_SOLID_COLOR:
+ pw.println("Letterbox background type is 'solid_color'");
+ break;
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+ pw.println("Letterbox background type is 'app_color_background'");
+ break;
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+ pw.println("Letterbox background type is 'app_color_background_floating'");
+ break;
+ default:
+ throw new AssertionError("Unexpected letterbox background type: " + backgroundType);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
+ final Color color;
+ String arg = getNextArgRequired();
+ try {
+ if ("reset".equals(arg)) {
+ mInternal.resetLetterboxBackgroundColor();
+ return 0;
+ }
+ color = Color.valueOf(Color.parseColor(arg));
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'reset' or color in #RRGGBB format should be provided as "
+ + "an argument " + e + " but got " + arg);
+ return -1;
+ }
+
+ mInternal.setLetterboxBackgroundColor(color);
+ return 0;
+ }
+
+ private int runGetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
+ final Color color = mInternal.getLetterboxBackgroundColor();
+ pw.println("Letterbox background color is " + Integer.toHexString(color.toArgb()));
+ return 0;
+ }
+
private int runReset(PrintWriter pw) throws RemoteException {
int displayId = getDisplayId(getNextArg());
@@ -611,6 +698,12 @@
// set-letterbox-activity-corners-radius
mInternal.resetLetterboxActivityCornersRadius();
+ // set-letterbox-background-type
+ mInternal.resetLetterboxBackgroundType();
+
+ // set-letterbox-background-color
+ mInternal.resetLetterboxBackgroundColor();
+
pw.println("Reset all settings for displayId=" + displayId);
return 0;
}
@@ -652,6 +745,16 @@
pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,");
pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be");
pw.println(" ignored and corners of the activity won't be rounded.");
+ pw.println(" set-letterbox-background-color [reset|colorName|'\\#RRGGBB']");
+ pw.println(" get-letterbox-background-color");
+ pw.println(" Color of letterbox background which is be used when letterbox background");
+ pw.println(" type is 'solid-color'. Use get(set)-letterbox-background-type to check");
+ pw.println(" and control letterbox background type. See Color#parseColor for allowed");
+ pw.println(" color formats (#RRGGBB and some colors by name, e.g. magenta or olive). ");
+ pw.println(" set-letterbox-background-type [reset|solid_color|app_color_background");
+ pw.println(" |app_color_background_floating]");
+ pw.println(" get-letterbox-background-type");
+ pw.println(" Type of background used in the letterbox mode.");
pw.println(" reset [-d DISPLAY_ID]");
pw.println(" Reset all override settings.");
if (!IS_USER) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1b81914..bd7116a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -40,6 +40,7 @@
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Slog;
@@ -112,6 +113,16 @@
}
@Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(TAG, e);
+ }
+ }
+
+ @Override
public void applyTransaction(WindowContainerTransaction t) {
enforceTaskPermission("applyTransaction()");
if (t == null) {
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
similarity index 96%
rename from services/core/java/com/android/server/policy/WindowOrientationListener.java
rename to services/core/java/com/android/server/wm/WindowOrientationListener.java
index 17e81da..da31bb2 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.policy;
+package com.android.server.wm;
import static com.android.server.wm.WindowOrientationListenerProto.ENABLED;
import static com.android.server.wm.WindowOrientationListenerProto.ROTATION;
@@ -31,6 +31,8 @@
import android.util.proto.ProtoOutputStream;
import android.view.Surface;
+import com.android.internal.util.FrameworkStatsLog;
+
import java.io.PrintWriter;
import java.util.List;
@@ -63,6 +65,7 @@
private OrientationJudge mOrientationJudge;
private int mCurrentRotation = -1;
private final Context mContext;
+ private final WindowManagerConstants mConstants;
private final Object mLock = new Object();
@@ -71,9 +74,11 @@
*
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
+ * @param wmService WindowManagerService to read the device config from.
*/
- public WindowOrientationListener(Context context, Handler handler) {
- this(context, handler, SensorManager.SENSOR_DELAY_UI);
+ public WindowOrientationListener(
+ Context context, Handler handler, WindowManagerService wmService) {
+ this(context, handler, wmService, SensorManager.SENSOR_DELAY_UI);
}
/**
@@ -81,6 +86,7 @@
*
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
+ * @param wmService WindowManagerService to read the device config from.
* @param rate at which sensor events are processed (see also
* {@link android.hardware.SensorManager SensorManager}). Use the default
* value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
@@ -88,10 +94,12 @@
*
* This constructor is private since no one uses it.
*/
- private WindowOrientationListener(Context context, Handler handler, int rate) {
+ private WindowOrientationListener(
+ Context context, Handler handler, WindowManagerService wmService, int rate) {
mContext = context;
mHandler = handler;
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mConstants = wmService.mConstants;
+ mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
Sensor wakeUpDeviceOrientationSensor = null;
@@ -497,7 +505,7 @@
private static final float MIN_ACCELERATION_MAGNITUDE =
SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
private static final float MAX_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
+ SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
// Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
// when screen is facing the sky or ground), we completely ignore orientation data
@@ -1072,8 +1080,14 @@
mDesiredRotation = reportedRotation;
newRotation = evaluateRotationChangeLocked();
}
- if (newRotation >=0) {
+ if (newRotation >= 0) {
onProposedRotationChanged(newRotation);
+ if (mConstants.mRawSensorLoggingEnabled) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.DEVICE_ROTATED,
+ event.timestamp,
+ rotationToLogEnum(reportedRotation));
+ }
}
}
@@ -1180,5 +1194,20 @@
}
}
};
+
+ private int rotationToLogEnum(int rotation) {
+ switch (rotation) {
+ case 0:
+ return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_0;
+ case 1:
+ return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_90;
+ case 2:
+ return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_180;
+ case 3:
+ return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_270;
+ default:
+ return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__UNKNOWN;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1597604..661118f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -212,6 +212,7 @@
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
@@ -296,8 +297,6 @@
final int mShowUserId;
/** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
final boolean mOwnerCanAddInternalSystemWindow;
- /** The owner has {@link android.Manifest.permission#USE_BACKGROUND_BLUR} */
- final boolean mOwnerCanUseBackgroundBlur;
final WindowId mWindowId;
WindowToken mToken;
// The same object as mToken if this is an app window and null for non-app windows.
@@ -440,6 +439,7 @@
float mGlobalScale=1;
float mLastGlobalScale=1;
float mInvGlobalScale=1;
+ float mOverrideScale = 1;
float mHScale=1, mVScale=1;
float mLastHScale=1, mLastVScale=1;
final Matrix mTmpMatrix = new Matrix();
@@ -647,9 +647,14 @@
boolean mSeamlesslyRotated = false;
/**
- * Indicates if this window is behind IME. Only windows behind IME can get insets from IME.
+ * The insets state of sources provided by windows above the current window.
*/
- boolean mBehindIme = false;
+ InsetsState mAboveInsetsState = new InsetsState();
+
+ /**
+ * The insets sources provided by this window.
+ */
+ ArrayMap<Integer, InsetsSource> mProvidedInsetsSources = new ArrayMap<>();
/**
* Surface insets from the previous call to relayout(), used to track
@@ -890,11 +895,9 @@
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
- int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
- boolean ownerCanUseBackgroundBlur) {
+ int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
- ownerCanAddInternalSystemWindow, ownerCanUseBackgroundBlur,
- new PowerManagerWrapper() {
+ ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
@Override
public void wakeUp(long time, @WakeReason int reason, String details) {
service.mPowerManager.wakeUp(time, reason, details);
@@ -910,7 +913,7 @@
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
- boolean ownerCanUseBackgroundBlur, PowerManagerWrapper powerManagerWrapper) {
+ PowerManagerWrapper powerManagerWrapper) {
super(service);
mTmpTransaction = service.mTransactionFactory.get();
mSession = s;
@@ -921,7 +924,6 @@
mOwnerUid = ownerId;
mShowUserId = showUserId;
mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
- mOwnerCanUseBackgroundBlur = ownerCanUseBackgroundBlur;
mWindowId = new WindowId(this);
mAttrs.copyFrom(a);
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
@@ -1008,6 +1010,8 @@
mLastRequestedWidth = 0;
mLastRequestedHeight = 0;
mLayer = 0;
+ mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
+ mAttrs.packageName, s.mUid);
// Make sure we initial all fields before adding to parentWindow, to prevent exception
// during onDisplayChanged.
@@ -1040,8 +1044,15 @@
mSession.windowAddedLocked(mAttrs.packageName);
}
+ /**
+ * @return {@code true} if the application runs in size compatibility mode or has an app level
+ * scaling override set.
+ * @see CompatModePackages#getCompatScale
+ * @see android.content.res.CompatibilityInfo#supportsScreen
+ * @see ActivityRecord#inSizeCompatMode()
+ */
boolean inSizeCompatMode() {
- return inSizeCompatMode(mAttrs, mActivityRecord);
+ return mOverrideScale != 1f || inSizeCompatMode(mAttrs, mActivityRecord);
}
/**
@@ -1676,7 +1687,13 @@
void prelayout() {
if (inSizeCompatMode()) {
- mGlobalScale = mToken.getSizeCompatScale();
+ if (mOverrideScale != 1f) {
+ mGlobalScale = mToken.hasSizeCompatBounds()
+ ? mToken.getSizeCompatScale() * mOverrideScale
+ : mOverrideScale;
+ } else {
+ mGlobalScale = mToken.getSizeCompatScale();
+ }
mInvGlobalScale = 1 / mGlobalScale;
} else {
mGlobalScale = mInvGlobalScale = 1;
@@ -2634,8 +2651,7 @@
// scaling but the existing logic doesn't expect that. The result is that the already-
// scaled region ends up getting sent to surfaceflinger which then applies the scale
// (again). Until this is resolved, apply an inverse-scale here.
- if (mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
- && mGlobalScale != 1.f) {
+ if (mInvGlobalScale != 1.f) {
region.scale(mInvGlobalScale);
}
@@ -5254,7 +5270,7 @@
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
- } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || isBlurEnabled())
+ } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || (mAttrs.flags & FLAG_BLUR_BEHIND) != 0)
&& isVisibleNow() && !mHidden) {
// Only show the Dimmer when the following is satisfied:
// 1. The window has the flag FLAG_DIM_BEHIND or background blur is requested
@@ -5263,15 +5279,13 @@
// 4. The WS is not hidden.
mIsDimming = true;
final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0;
- final int blurRadius = isBlurEnabled() ? mAttrs.backgroundBlurRadius : 0;
- getDimmer().dimBelow(getSyncTransaction(), this, dimAmount, blurRadius);
+ final int blurRadius =
+ (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 ? mAttrs.blurBehindRadius : 0;
+ getDimmer().dimBelow(
+ getSyncTransaction(), this, mAttrs.dimAmount, mAttrs.blurBehindRadius);
}
}
- private boolean isBlurEnabled() {
- return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mOwnerCanUseBackgroundBlur;
- }
-
/**
* Notifies SF about the priority of the window, if it changed. SF then uses this information
* to decide which window's desired rendering rate should have a priority when deciding about
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 995cfe9..9a8942b 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -12,6 +12,9 @@
per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com
per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com
+# BatteryStats
+per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
+
per-file Android.bp = file:platform/build/soong:/OWNERS
per-file com_android_server_Usb* = file:/services/usb/OWNERS
per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index c3e7c7a..16eaa77 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -55,15 +55,8 @@
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::power::stats::V1_0::IPowerStats;
-using android::hardware::power::V1_0::PowerStatePlatformSleepState;
-using android::hardware::power::V1_0::PowerStateVoter;
-using android::hardware::power::V1_0::Status;
-using android::hardware::power::V1_1::PowerStateSubsystem;
-using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
using android::system::suspend::BnSuspendCallback;
using android::system::suspend::ISuspendControlService;
-using IPowerV1_1 = android::hardware::power::V1_1::IPower;
-using IPowerV1_0 = android::hardware::power::V1_0::IPower;
namespace android
{
@@ -74,23 +67,9 @@
static sem_t wakeup_sem;
extern sp<ISuspendControlService> getSuspendControl();
-// Java methods used in getLowPowerStats
-static jmethodID jgetAndUpdatePlatformState = NULL;
-static jmethodID jgetSubsystem = NULL;
-static jmethodID jputVoter = NULL;
-static jmethodID jputState = NULL;
-
std::mutex gPowerStatsHalMutex;
-std::unordered_map<uint32_t, std::string> gPowerStatsHalEntityNames = {};
-std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>>
- gPowerStatsHalStateNames = {};
-std::vector<uint32_t> gPowerStatsHalPlatformIds = {};
-std::vector<uint32_t> gPowerStatsHalSubsystemIds = {};
sp<IPowerStats> gPowerStatsHalV1_0 = nullptr;
-std::function<void(JNIEnv*, jobject)> gGetLowPowerStatsImpl = {};
-std::function<jint(JNIEnv*, jobject)> gGetPlatformLowPowerStatsImpl = {};
-std::function<jint(JNIEnv*, jobject)> gGetSubsystemLowPowerStatsImpl = {};
std::function<void(JNIEnv*, jobject)> gGetRailEnergyPowerStatsImpl = {};
// Cellular/Wifi power monitor rail information
@@ -222,68 +201,14 @@
return true;
}
-static bool checkPowerHalResult(const Return<void>& ret, const char* function) {
- if (!ret.isOk()) {
- ALOGE("%s failed: requested HAL service not available.", function);
- power::PowerHalLoader::unloadAll();
- return false;
- }
- return true;
-}
-
// gPowerStatsHalV1_0 must not be null
static bool initializePowerStatsLocked() EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
using android::hardware::power::stats::V1_0::Status;
- using android::hardware::power::stats::V1_0::PowerEntityType;
// Clear out previous content if we are re-initializing
- gPowerStatsHalEntityNames.clear();
- gPowerStatsHalStateNames.clear();
- gPowerStatsHalPlatformIds.clear();
- gPowerStatsHalSubsystemIds.clear();
gPowerStatsHalRailNames.clear();
Return<void> ret;
- ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
- if (status != Status::SUCCESS) {
- ALOGE("Error getting power entity info");
- return;
- }
-
- // construct lookup table of powerEntityId to power entity name
- // also construct vector of platform and subsystem IDs
- for (auto info : infos) {
- gPowerStatsHalEntityNames.emplace(info.powerEntityId, info.powerEntityName);
- if (info.type == PowerEntityType::POWER_DOMAIN) {
- gPowerStatsHalPlatformIds.emplace_back(info.powerEntityId);
- } else {
- gPowerStatsHalSubsystemIds.emplace_back(info.powerEntityId);
- }
- }
- });
- if (!checkPowerStatsHalResultLocked(ret, __func__)) {
- return false;
- }
-
- ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
- if (status != Status::SUCCESS) {
- ALOGE("Error getting state info");
- return;
- }
-
- // construct lookup table of powerEntityId, powerEntityStateId to power entity state name
- for (auto stateSpace : stateSpaces) {
- std::unordered_map<uint32_t, std::string> stateNames = {};
- for (auto state : stateSpace.states) {
- stateNames.emplace(state.powerEntityStateId,
- state.powerEntityStateName);
- }
- gPowerStatsHalStateNames.emplace(stateSpace.powerEntityId, stateNames);
- }
- });
- if (!checkPowerStatsHalResultLocked(ret, __func__)) {
- return false;
- }
// Get Power monitor rails available
ret = gPowerStatsHalV1_0->getRailInfo([](auto rails, auto status) {
@@ -306,7 +231,7 @@
return false;
}
- return (!gPowerStatsHalEntityNames.empty()) && (!gPowerStatsHalStateNames.empty());
+ return true;
}
static bool getPowerStatsHalLocked() EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
@@ -333,196 +258,6 @@
return true;
}
-static void getPowerStatsHalLowPowerDataLocked(JNIEnv* env, jobject jrpmStats)
- EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
- using android::hardware::power::stats::V1_0::Status;
-
- if (!getPowerStatsHalLocked()) {
- ALOGE("failed to get low power stats");
- return;
- }
-
- // Get power entity state residency data
- bool success = false;
- Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({},
- [&env, &jrpmStats, &success](auto results, auto status) {
- if (status == Status::NOT_SUPPORTED) {
- ALOGW("getPowerEntityStateResidencyData is not supported");
- success = false;
- return;
- }
-
- for (auto result : results) {
- jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
- env->NewStringUTF(gPowerStatsHalEntityNames.at(result.powerEntityId).c_str()));
- if (jsubsystem == NULL) {
- ALOGE("The rpmstats jni jobject jsubsystem is null.");
- return;
- }
- for (auto stateResidency : result.stateResidencyData) {
-
- env->CallVoidMethod(jsubsystem, jputState,
- env->NewStringUTF(gPowerStatsHalStateNames.at(result.powerEntityId)
- .at(stateResidency.powerEntityStateId).c_str()),
- stateResidency.totalTimeInStateMs,
- stateResidency.totalStateEntryCount);
- }
- }
- success = true;
- });
- checkPowerStatsHalResultLocked(ret, __func__);
- if (!success) {
- ALOGE("getPowerEntityStateResidencyData failed");
- }
-}
-
-static jint getPowerStatsHalPlatformDataLocked(JNIEnv* env, jobject outBuf)
- EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
- using android::hardware::power::stats::V1_0::Status;
- using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
- using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
-
- if (!getPowerStatsHalLocked()) {
- ALOGE("failed to get low power stats");
- return -1;
- }
-
- char *output = (char*)env->GetDirectBufferAddress(outBuf);
- char *offset = output;
- int remaining = (int)env->GetDirectBufferCapacity(outBuf);
- int total_added = -1;
-
- // Get power entity state residency data
- Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
- gPowerStatsHalPlatformIds,
- [&offset, &remaining, &total_added](auto results, auto status) {
- if (status == Status::NOT_SUPPORTED) {
- ALOGW("getPowerEntityStateResidencyData is not supported");
- return;
- }
-
- for (size_t i = 0; i < results.size(); i++) {
- const PowerEntityStateResidencyResult& result = results[i];
-
- for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
- const PowerEntityStateResidencyData& stateResidency =
- result.stateResidencyData[j];
- int added = snprintf(offset, remaining,
- "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
- j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
- .at(stateResidency.powerEntityStateId).c_str(),
- stateResidency.totalTimeInStateMs,
- stateResidency.totalStateEntryCount);
- if (added < 0) {
- break;
- }
- if (added > remaining) {
- added = remaining;
- }
- offset += added;
- remaining -= added;
- total_added += added;
- }
- if (remaining <= 0) {
- /* rewrite NULL character*/
- offset--;
- total_added--;
- ALOGE("power.stats Hal: buffer not enough");
- break;
- }
- }
- });
- if (!checkPowerStatsHalResultLocked(ret, __func__)) {
- return -1;
- }
-
- total_added += 1;
- return total_added;
-}
-
-static jint getPowerStatsHalSubsystemDataLocked(JNIEnv* env, jobject outBuf)
- EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
- using android::hardware::power::stats::V1_0::Status;
- using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
- using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
-
- if (!getPowerStatsHalLocked()) {
- ALOGE("failed to get low power stats");
- return -1;
- }
-
- char *output = (char*)env->GetDirectBufferAddress(outBuf);
- char *offset = output;
- int remaining = (int)env->GetDirectBufferCapacity(outBuf);
- int total_added = -1;
-
- // Get power entity state residency data
- Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
- gPowerStatsHalSubsystemIds,
- [&offset, &remaining, &total_added](auto results, auto status) {
- if (status == Status::NOT_SUPPORTED) {
- ALOGW("getPowerEntityStateResidencyData is not supported");
- return;
- }
-
- int added = snprintf(offset, remaining, "SubsystemPowerState ");
- offset += added;
- remaining -= added;
- total_added += added;
-
- for (size_t i = 0; i < results.size(); i++) {
- const PowerEntityStateResidencyResult& result = results[i];
- added = snprintf(offset, remaining, "subsystem_%zu name=%s ",
- i + 1, gPowerStatsHalEntityNames.at(result.powerEntityId).c_str());
- if (added < 0) {
- break;
- }
-
- if (added > remaining) {
- added = remaining;
- }
-
- offset += added;
- remaining -= added;
- total_added += added;
-
- for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
- const PowerEntityStateResidencyData& stateResidency =
- result.stateResidencyData[j];
- added = snprintf(offset, remaining,
- "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%"
- PRIu64 " ", j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
- .at(stateResidency.powerEntityStateId).c_str(),
- stateResidency.totalTimeInStateMs,
- stateResidency.totalStateEntryCount,
- stateResidency.lastEntryTimestampMs);
- if (added < 0) {
- break;
- }
- if (added > remaining) {
- added = remaining;
- }
- offset += added;
- remaining -= added;
- total_added += added;
- }
- if (remaining <= 0) {
- /* rewrite NULL character*/
- offset--;
- total_added--;
- ALOGE("power.stats Hal: buffer not enough");
- break;
- }
- }
- });
- if (!checkPowerStatsHalResultLocked(ret, __func__)) {
- return -1;
- }
-
- total_added += 1;
- return total_added;
-}
-
static void getPowerStatsHalRailEnergyDataLocked(JNIEnv* env, jobject jrailStats)
EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
using android::hardware::power::stats::V1_0::Status;
@@ -568,325 +303,17 @@
}
}
-static void getPowerHalLowPowerData(JNIEnv* env, jobject jrpmStats) {
- sp<IPowerV1_0> powerHalV1_0 = power::PowerHalLoader::loadHidlV1_0();
- if (powerHalV1_0 == nullptr) {
- ALOGE("Power Hal not loaded");
- return;
- }
-
- Return<void> ret = powerHalV1_0->getPlatformLowPowerStats(
- [&env, &jrpmStats](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
-
- if (status != Status::SUCCESS) return;
-
- for (size_t i = 0; i < states.size(); i++) {
- const PowerStatePlatformSleepState& state = states[i];
-
- jobject jplatformState = env->CallObjectMethod(jrpmStats,
- jgetAndUpdatePlatformState,
- env->NewStringUTF(state.name.c_str()),
- state.residencyInMsecSinceBoot,
- state.totalTransitions);
- if (jplatformState == NULL) {
- ALOGE("The rpmstats jni jobject jplatformState is null.");
- return;
- }
-
- for (size_t j = 0; j < state.voters.size(); j++) {
- const PowerStateVoter& voter = state.voters[j];
- env->CallVoidMethod(jplatformState, jputVoter,
- env->NewStringUTF(voter.name.c_str()),
- voter.totalTimeInMsecVotedForSinceBoot,
- voter.totalNumberOfTimesVotedSinceBoot);
- }
- }
- });
- if (!checkPowerHalResult(ret, "getPlatformLowPowerStats")) {
- return;
- }
-
- // Trying to get IPower 1.1, this will succeed only for devices supporting 1.1
- sp<IPowerV1_1> powerHal_1_1 = power::PowerHalLoader::loadHidlV1_1();
- if (powerHal_1_1 == nullptr) {
- // This device does not support IPower@1.1, exiting gracefully
- return;
- }
- ret = powerHal_1_1->getSubsystemLowPowerStats(
- [&env, &jrpmStats](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
-
- if (status != Status::SUCCESS) return;
-
- if (subsystems.size() > 0) {
- for (size_t i = 0; i < subsystems.size(); i++) {
- const PowerStateSubsystem &subsystem = subsystems[i];
-
- jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
- env->NewStringUTF(subsystem.name.c_str()));
- if (jsubsystem == NULL) {
- ALOGE("The rpmstats jni jobject jsubsystem is null.");
- return;
- }
-
- for (size_t j = 0; j < subsystem.states.size(); j++) {
- const PowerStateSubsystemSleepState& state = subsystem.states[j];
- env->CallVoidMethod(jsubsystem, jputState,
- env->NewStringUTF(state.name.c_str()),
- state.residencyInMsecSinceBoot,
- state.totalTransitions);
- }
- }
- }
- });
- checkPowerHalResult(ret, "getSubsystemLowPowerStats");
-}
-
-static jint getPowerHalPlatformData(JNIEnv* env, jobject outBuf) {
- char *output = (char*)env->GetDirectBufferAddress(outBuf);
- char *offset = output;
- int remaining = (int)env->GetDirectBufferCapacity(outBuf);
- int total_added = -1;
-
- {
- sp<IPowerV1_0> powerHalV1_0 = power::PowerHalLoader::loadHidlV1_0();
- if (powerHalV1_0 == nullptr) {
- ALOGE("Power Hal not loaded");
- return -1;
- }
-
- Return<void> ret = powerHalV1_0->getPlatformLowPowerStats(
- [&offset, &remaining, &total_added](hidl_vec<PowerStatePlatformSleepState> states,
- Status status) {
- if (status != Status::SUCCESS)
- return;
- for (size_t i = 0; i < states.size(); i++) {
- int added;
- const PowerStatePlatformSleepState& state = states[i];
-
- added = snprintf(offset, remaining,
- "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
- i + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
- state.totalTransitions);
- if (added < 0) {
- break;
- }
- if (added > remaining) {
- added = remaining;
- }
- offset += added;
- remaining -= added;
- total_added += added;
-
- for (size_t j = 0; j < state.voters.size(); j++) {
- const PowerStateVoter& voter = state.voters[j];
- added = snprintf(offset, remaining,
- "voter_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
- j + 1, voter.name.c_str(),
- voter.totalTimeInMsecVotedForSinceBoot,
- voter.totalNumberOfTimesVotedSinceBoot);
- if (added < 0) {
- break;
- }
- if (added > remaining) {
- added = remaining;
- }
- offset += added;
- remaining -= added;
- total_added += added;
- }
-
- if (remaining <= 0) {
- /* rewrite NULL character*/
- offset--;
- total_added--;
- ALOGE("PowerHal: buffer not enough");
- break;
- }
- }
- }
- );
-
- if (!checkPowerHalResult(ret, "getPlatformLowPowerStats")) {
- return -1;
- }
- }
- *offset = 0;
- total_added += 1;
- return total_added;
-}
-
-static jint getPowerHalSubsystemData(JNIEnv* env, jobject outBuf) {
- char *output = (char*)env->GetDirectBufferAddress(outBuf);
- char *offset = output;
- int remaining = (int)env->GetDirectBufferCapacity(outBuf);
- int total_added = -1;
-
- // This is a IPower 1.1 API
- sp<IPowerV1_1> powerHal_1_1 = nullptr;
-
- {
- // Trying to get 1.1, this will succeed only for devices supporting 1.1
- powerHal_1_1 = power::PowerHalLoader::loadHidlV1_1();
- if (powerHal_1_1 == nullptr) {
- //This device does not support IPower@1.1, exiting gracefully
- return 0;
- }
-
- Return<void> ret = powerHal_1_1->getSubsystemLowPowerStats(
- [&offset, &remaining, &total_added](hidl_vec<PowerStateSubsystem> subsystems,
- Status status) {
-
- if (status != Status::SUCCESS)
- return;
-
- if (subsystems.size() > 0) {
- int added = snprintf(offset, remaining, "SubsystemPowerState ");
- offset += added;
- remaining -= added;
- total_added += added;
-
- for (size_t i = 0; i < subsystems.size(); i++) {
- const PowerStateSubsystem &subsystem = subsystems[i];
-
- added = snprintf(offset, remaining,
- "subsystem_%zu name=%s ", i + 1, subsystem.name.c_str());
- if (added < 0) {
- break;
- }
-
- if (added > remaining) {
- added = remaining;
- }
-
- offset += added;
- remaining -= added;
- total_added += added;
-
- for (size_t j = 0; j < subsystem.states.size(); j++) {
- const PowerStateSubsystemSleepState& state = subsystem.states[j];
- added = snprintf(offset, remaining,
- "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%" PRIu64 " ",
- j + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
- state.totalTransitions, state.lastEntryTimestampMs);
- if (added < 0) {
- break;
- }
-
- if (added > remaining) {
- added = remaining;
- }
-
- offset += added;
- remaining -= added;
- total_added += added;
- }
-
- if (remaining <= 0) {
- /* rewrite NULL character*/
- offset--;
- total_added--;
- ALOGE("PowerHal: buffer not enough");
- break;
- }
- }
- }
- }
- );
-
- if (!checkPowerHalResult(ret, "getSubsystemLowPowerStats")) {
- return -1;
- }
- }
-
- *offset = 0;
- total_added += 1;
- return total_added;
-}
-
static void setUpPowerStatsLocked() EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
// First see if power.stats HAL is available. Fall back to power HAL if
// power.stats HAL is unavailable.
if (IPowerStats::getService() != nullptr) {
ALOGI("Using power.stats HAL");
- gGetLowPowerStatsImpl = getPowerStatsHalLowPowerDataLocked;
- gGetPlatformLowPowerStatsImpl = getPowerStatsHalPlatformDataLocked;
- gGetSubsystemLowPowerStatsImpl = getPowerStatsHalSubsystemDataLocked;
gGetRailEnergyPowerStatsImpl = getPowerStatsHalRailEnergyDataLocked;
- } else if (IPowerV1_0::getService() != nullptr) {
- ALOGI("Using power HAL");
- gGetLowPowerStatsImpl = getPowerHalLowPowerData;
- gGetPlatformLowPowerStatsImpl = getPowerHalPlatformData;
- gGetSubsystemLowPowerStatsImpl = getPowerHalSubsystemData;
+ } else {
gGetRailEnergyPowerStatsImpl = NULL;
}
}
-static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) {
- if (jrpmStats == NULL) {
- jniThrowException(env, "java/lang/NullPointerException",
- "The rpmstats jni input jobject jrpmStats is null.");
- return;
- }
- if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL
- || jputVoter == NULL || jputState == NULL) {
- ALOGE("A rpmstats jni jmethodID is null.");
- return;
- }
-
- std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
- if (!gGetLowPowerStatsImpl) {
- setUpPowerStatsLocked();
- }
-
- if (gGetLowPowerStatsImpl) {
- return gGetLowPowerStatsImpl(env, jrpmStats);
- }
-
- ALOGE("Unable to load Power Hal or power.stats HAL");
- return;
-}
-
-static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
- if (outBuf == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", "null argument");
- return -1;
- }
-
- std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
- if (!gGetPlatformLowPowerStatsImpl) {
- setUpPowerStatsLocked();
- }
-
- if (gGetPlatformLowPowerStatsImpl) {
- return gGetPlatformLowPowerStatsImpl(env, outBuf);
- }
-
- ALOGE("Unable to load Power Hal or power.stats HAL");
- return -1;
-}
-
-static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
- if (outBuf == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", "null argument");
- return -1;
- }
-
- std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
- if (!gGetSubsystemLowPowerStatsImpl) {
- setUpPowerStatsLocked();
- }
-
- if (gGetSubsystemLowPowerStatsImpl) {
- return gGetSubsystemLowPowerStatsImpl(env, outBuf);
- }
-
- ALOGE("Unable to load Power Hal or power.stats HAL");
- return -1;
-}
-
static void getRailEnergyPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrailStats) {
if (jrailStats == NULL) {
jniThrowException(env, "java/lang/NullPointerException",
@@ -920,9 +347,6 @@
static const JNINativeMethod method_table[] = {
{ "nativeWaitWakeup", "(Ljava/nio/ByteBuffer;)I", (void*)nativeWaitWakeup },
- { "getLowPowerStats", "(Lcom/android/internal/os/RpmStats;)V", (void*)getLowPowerStats },
- { "getPlatformLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getPlatformLowPowerStats },
- { "getSubsystemLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getSubsystemLowPowerStats },
{ "getRailEnergyPowerStats", "(Lcom/android/internal/os/RailStats;)V",
(void*)getRailEnergyPowerStats },
};
@@ -930,24 +354,10 @@
int register_android_server_BatteryStatsService(JNIEnv *env)
{
// get java classes and methods
- jclass clsRpmStats = env->FindClass("com/android/internal/os/RpmStats");
- jclass clsPowerStatePlatformSleepState =
- env->FindClass("com/android/internal/os/RpmStats$PowerStatePlatformSleepState");
- jclass clsPowerStateSubsystem =
- env->FindClass("com/android/internal/os/RpmStats$PowerStateSubsystem");
jclass clsRailStats = env->FindClass("com/android/internal/os/RailStats");
- if (clsRpmStats == NULL || clsPowerStatePlatformSleepState == NULL
- || clsPowerStateSubsystem == NULL || clsRailStats == NULL) {
+ if (clsRailStats == NULL) {
ALOGE("A rpmstats jni jclass is null.");
} else {
- jgetAndUpdatePlatformState = env->GetMethodID(clsRpmStats, "getAndUpdatePlatformState",
- "(Ljava/lang/String;JI)Lcom/android/internal/os/RpmStats$PowerStatePlatformSleepState;");
- jgetSubsystem = env->GetMethodID(clsRpmStats, "getSubsystem",
- "(Ljava/lang/String;)Lcom/android/internal/os/RpmStats$PowerStateSubsystem;");
- jputVoter = env->GetMethodID(clsPowerStatePlatformSleepState, "putVoter",
- "(Ljava/lang/String;JI)V");
- jputState = env->GetMethodID(clsPowerStateSubsystem, "putState",
- "(Ljava/lang/String;JI)V");
jupdateRailData = env->GetMethodID(clsRailStats, "updateRailData",
"(JLjava/lang/String;Ljava/lang/String;JJ)V");
jsetRailStatsAvailability = env->GetMethodID(clsRailStats, "setRailStatsAvailability",
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 156ef79..31cc295 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -123,7 +123,7 @@
}
return -1;
}
-static bool getAnyPageAdvice(const Vma& vma) {
+static int getAnyPageAdvice(const Vma& vma) {
if (vma.inode == 0 && !vma.is_shared) {
return MADV_PAGEOUT;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index dc15b07..5b587e9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -26,14 +26,14 @@
// Log debug messages about InputDispatcherPolicy
#define DEBUG_INPUT_DISPATCHER_POLICY 0
-
-#include <atomic>
-#include <cinttypes>
-#include <limits.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
+#include <android/os/IInputConstants.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
+#include <limits.h>
+#include <atomic>
+#include <cinttypes>
#include <utils/Log.h>
#include <utils/Looper.h>
@@ -46,6 +46,7 @@
#include <input/SpriteController.h>
#include <ui/Region.h>
+#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
#include <inputflinger/InputManager.h>
#include <android_os_MessageQueue.h>
@@ -1908,6 +1909,20 @@
return vibIdArray;
}
+static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryCapacity(deviceId);
+ return static_cast<jint>(ret.value_or(android::os::IInputConstants::INVALID_BATTERY_CAPACITY));
+}
+
+static jint nativeGetBatteryStatus(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryStatus(deviceId);
+ return static_cast<jint>(ret.value_or(BATTERY_STATUS_UNKNOWN));
+}
+
static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2163,6 +2178,8 @@
{"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
{"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
{"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
+ {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
+ {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
{"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
{"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
{"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 7b379e5..5a5b0a8 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -22,6 +22,7 @@
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <core_jni_helpers.h>
+#include <cutils/multiuser.h>
#include <cutils/trace.h>
#include <endian.h>
#include <nativehelper/JNIHelp.h>
@@ -375,6 +376,9 @@
}
private:
+ // Bitmask of supported features.
+ DataLoaderFeatures getFeatures() const final { return DATA_LOADER_FEATURE_UID; }
+
// Lifecycle.
bool onCreate(const android::dataloader::DataLoaderParams& params,
android::dataloader::FilesystemConnectorPtr ifs,
@@ -554,51 +558,6 @@
return true;
}
- // Read tracing.
- struct TracedRead {
- uint64_t timestampUs;
- android::dataloader::FileId fileId;
- uint32_t firstBlockIdx;
- uint32_t count;
- };
-
- void onPageReads(android::dataloader::PageReads pageReads) final {
- auto trace = atrace_is_tag_enabled(ATRACE_TAG);
- if (CC_LIKELY(!trace)) {
- return;
- }
-
- TracedRead last = {};
- for (auto&& read : pageReads) {
- if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) {
- traceRead(last);
- last = TracedRead{
- .timestampUs = read.bootClockTsUs,
- .fileId = read.id,
- .firstBlockIdx = (uint32_t)read.block,
- .count = 1,
- };
- } else {
- ++last.count;
- }
- }
- traceRead(last);
- }
-
- void traceRead(const TracedRead& read) {
- if (!read.count) {
- return;
- }
-
- FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
- auto str = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
- static_cast<long long>(read.firstBlockIdx),
- static_cast<long long>(read.count),
- static_cast<int>(fileIdx));
- ATRACE_BEGIN(str.c_str());
- ATRACE_END();
- }
-
// Streaming.
bool initStreaming(unique_fd inout, MetadataMode mode) {
mEventFd.reset(eventfd(0, EFD_CLOEXEC));
@@ -634,7 +593,10 @@
}
// IFS callbacks.
- void onPendingReads(dataloader::PendingReads pendingReads) final {
+ void onPendingReads(dataloader::PendingReads pendingReads) final {}
+ void onPageReads(dataloader::PageReads pageReads) final {}
+
+ void onPendingReadsWithUid(dataloader::PendingReadsWithUid pendingReads) final {
std::lock_guard lock{mOutFdLock};
if (mOutFd < 0) {
return;
@@ -660,6 +622,67 @@
}
}
+ // Read tracing.
+ struct TracedRead {
+ uint64_t timestampUs;
+ android::dataloader::FileId fileId;
+ android::dataloader::Uid uid;
+ uint32_t firstBlockIdx;
+ uint32_t count;
+ };
+
+ void onPageReadsWithUid(dataloader::PageReadsWithUid pageReads) final {
+ auto trace = atrace_is_tag_enabled(ATRACE_TAG);
+ if (CC_LIKELY(!trace)) {
+ return;
+ }
+
+ TracedRead last = {};
+ for (auto&& read : pageReads) {
+ if (read.id != last.fileId || read.uid != last.uid ||
+ read.block != last.firstBlockIdx + last.count) {
+ traceRead(last);
+ last = TracedRead{
+ .timestampUs = read.bootClockTsUs,
+ .fileId = read.id,
+ .uid = read.uid,
+ .firstBlockIdx = (uint32_t)read.block,
+ .count = 1,
+ };
+ } else {
+ ++last.count;
+ }
+ }
+ traceRead(last);
+ }
+
+ void traceRead(const TracedRead& read) {
+ if (!read.count) {
+ return;
+ }
+
+ FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
+
+ std::string trace;
+ if (read.uid != kIncFsNoUid) {
+ auto appId = multiuser_get_app_id(read.uid);
+ auto userId = multiuser_get_user_id(read.uid);
+ trace = android::base::
+ StringPrintf("page_read: index=%lld count=%lld file=%d appid=%d userid=%d",
+ static_cast<long long>(read.firstBlockIdx),
+ static_cast<long long>(read.count), static_cast<int>(fileIdx),
+ static_cast<int>(appId), static_cast<int>(userId));
+ } else {
+ trace = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
+ static_cast<long long>(read.firstBlockIdx),
+ static_cast<long long>(read.count),
+ static_cast<int>(fileIdx));
+ }
+
+ ATRACE_BEGIN(trace.c_str());
+ ATRACE_END();
+ }
+
void receiver(unique_fd inout, MetadataMode mode) {
std::vector<uint8_t> data;
std::vector<IncFsDataBlock> instructions;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 1194099..11e4db5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -120,11 +120,11 @@
@NonNull String callerPackage, @NonNull String enterpriseId, int userId) {}
public UserHandle createAndProvisionManagedProfile(
- @NonNull ManagedProfileProvisioningParams provisioningParams) {
+ @NonNull ManagedProfileProvisioningParams provisioningParams, String callerPackage) {
return null;
}
public void provisionFullyManagedDevice(
- FullyManagedDeviceProvisioningParams provisioningParams) {
+ FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 15bc93e..8b2beb2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -43,10 +43,14 @@
@GuardedBy("mLock")
private final SparseIntArray mPasswordQuality = new SparseIntArray();
+ @GuardedBy("mLock")
+ private final SparseIntArray mPermissionPolicy = new SparseIntArray();
+
public void onUserRemoved(int userHandle) {
synchronized (mLock) {
mScreenCaptureDisabled.delete(userHandle);
mPasswordQuality.delete(userHandle);
+ mPermissionPolicy.delete(userHandle);
}
}
@@ -78,12 +82,28 @@
}
}
+ @Override
+ public int getPermissionPolicy(@UserIdInt int userHandle) {
+ synchronized (mLock) {
+ return mPermissionPolicy.get(userHandle,
+ DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+ }
+ }
+
+ /** Update the permission policy for the given user. */
+ public void setPermissionPolicy(@UserIdInt int userHandle, int policy) {
+ synchronized (mLock) {
+ mPermissionPolicy.put(userHandle, policy);
+ }
+ }
+
/** Dump content */
public void dump(IndentingPrintWriter pw) {
pw.println("Device policy cache:");
pw.increaseIndent();
pw.println("Screen capture disabled: " + mScreenCaptureDisabled.toString());
pw.println("Password quality: " + mPasswordQuality.toString());
+ pw.println("Permission policy: " + mPermissionPolicy.toString());
pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c3bb757..3d2e5de 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -35,10 +35,8 @@
import static android.app.admin.DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.CODE_NONSYSTEM_USER_EXISTS;
import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT;
import static android.app.admin.DevicePolicyManager.CODE_OK;
import static android.app.admin.DevicePolicyManager.CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
-import static android.app.admin.DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.CODE_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
@@ -94,6 +92,7 @@
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
+import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -130,6 +129,7 @@
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -159,6 +159,7 @@
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicySafetyChecker;
import android.app.admin.DeviceStateCache;
@@ -560,6 +561,21 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long USE_SET_LOCATION_ENABLED = 117835097L;
+ // Only add to the end of the list. Do not change or rearrange these values, that will break
+ // historical data. Do not use negative numbers or zero, logger only handles positive
+ // integers.
+ private static final int COPY_ACCOUNT_SUCCEEDED = 1;
+ private static final int COPY_ACCOUNT_FAILED = 2;
+ private static final int COPY_ACCOUNT_TIMED_OUT = 3;
+ private static final int COPY_ACCOUNT_EXCEPTION = 4;
+
+ @IntDef({
+ COPY_ACCOUNT_SUCCEEDED,
+ COPY_ACCOUNT_FAILED,
+ COPY_ACCOUNT_TIMED_OUT,
+ COPY_ACCOUNT_EXCEPTION})
+ private @interface CopyAccountStatus {}
+
/**
* Admin apps targeting Android S+ may not use
* {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
@@ -1069,30 +1085,35 @@
* @throws UnsafeStateException if it's not safe to execute the operation.
*/
private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) {
- if (!canExecute(operation)) {
- if (mSafetyChecker == null) {
- // Happens on CTS after it's set just once (by OneTimeSafetyChecker)
- throw new UnsafeStateException(operation);
- }
- // Let mSafetyChecker customize it (for example, by explaining how to retry)
- throw mSafetyChecker.newUnsafeStateException(operation);
+ int reason = getUnsafeOperationReason(operation);
+ if (reason == UNSAFE_OPERATION_REASON_NONE) return;
+
+ if (mSafetyChecker == null) {
+ // Happens on CTS after it's set just once (by OneTimeSafetyChecker)
+ throw new UnsafeStateException(operation, reason);
}
+ // Let mSafetyChecker customize it (for example, by explaining how to retry)
+ throw mSafetyChecker.newUnsafeStateException(operation, reason);
}
/**
- * Returns whether it's safe to execute the given {@code operation}.
+ * Returns whether it's safe to execute the given {@code operation}, and why.
*/
- boolean canExecute(@DevicePolicyOperation int operation) {
- return mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation);
+ @UnsafeOperationReason
+ int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
+ return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE
+ : mSafetyChecker.getUnsafeOperationReason(operation);
}
@Override
- public void setNextOperationSafety(@DevicePolicyOperation int operation, boolean safe) {
+ public void setNextOperationSafety(@DevicePolicyOperation int operation,
+ @UnsafeOperationReason int reason) {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
- Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %b)",
- DevicePolicyManager.operationToString(operation), safe));
- mSafetyChecker = new OneTimeSafetyChecker(this, operation, safe);
+ Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)",
+ DevicePolicyManager.operationToString(operation),
+ DevicePolicyManager.unsafeOperationReasonToString(reason)));
+ mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason);
}
/**
@@ -1380,11 +1401,6 @@
SystemProperties.set(key, value);
}
- // TODO (b/137101239): clean up split system user codes
- boolean userManagerIsSplitSystemUser() {
- return UserManager.isSplitSystemUser();
- }
-
boolean userManagerIsHeadlessSystemUserMode() {
return UserManager.isHeadlessSystemUserMode();
}
@@ -2942,6 +2958,7 @@
// reading the value during user switch, due to onStartUser() being asynchronous.
updatePasswordQualityCacheForUserGroup(
userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
+ updatePermissionPolicyCache(userId);
startOwnerService(userId, "start-user");
}
@@ -7404,48 +7421,18 @@
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
+ // TODO (b/137101239): remove this method in follow-up CL
+ // since it's only used for split system user.
@Override
public void setForceEphemeralUsers(ComponentName who, boolean forceEphemeralUsers) {
- if (!mHasFeature) {
- return;
- }
- Objects.requireNonNull(who, "ComponentName is null");
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
- // Allow setting this policy to true only if there is a split system user.
- if (forceEphemeralUsers && !mInjector.userManagerIsSplitSystemUser()) {
- throw new UnsupportedOperationException(
- "Cannot force ephemeral users on systems without split system user.");
- }
- boolean removeAllUsers = false;
- synchronized (getLockObject()) {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) {
- deviceOwner.forceEphemeralUsers = forceEphemeralUsers;
- saveSettingsLocked(caller.getUserId());
- mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers);
- removeAllUsers = forceEphemeralUsers;
- }
- }
- if (removeAllUsers) {
- mInjector.binderWithCleanCallingIdentity(() -> mUserManagerInternal.removeAllUsers());
- }
+ throw new UnsupportedOperationException("This method was used by split system user only.");
}
+ // TODO (b/137101239): remove this method in follow-up CL
+ // since it's only used for split system user.
@Override
public boolean getForceEphemeralUsers(ComponentName who) {
- if (!mHasFeature) {
- return false;
- }
- Objects.requireNonNull(who, "ComponentName is null");
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
- synchronized (getLockObject()) {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- return deviceOwner.forceEphemeralUsers;
- }
+ throw new UnsupportedOperationException("This method was used by split system user only.");
}
@Override
@@ -12498,11 +12485,13 @@
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY);
+ final int forUser = caller.getUserId();
synchronized (getLockObject()) {
- DevicePolicyData userPolicy = getUserData(caller.getUserId());
+ DevicePolicyData userPolicy = getUserData(forUser);
if (userPolicy.mPermissionPolicy != policy) {
userPolicy.mPermissionPolicy = policy;
- saveSettingsLocked(caller.getUserId());
+ mPolicyCache.setPermissionPolicy(forUser, policy);
+ saveSettingsLocked(forUser);
}
}
DevicePolicyEventLogger
@@ -12513,13 +12502,17 @@
.write();
}
+ private void updatePermissionPolicyCache(int userId) {
+ synchronized (getLockObject()) {
+ DevicePolicyData userPolicy = getUserData(userId);
+ mPolicyCache.setPermissionPolicy(userId, userPolicy.mPermissionPolicy);
+ }
+ }
+
@Override
public int getPermissionPolicy(ComponentName admin) throws RemoteException {
int userId = UserHandle.getCallingUserId();
- synchronized (getLockObject()) {
- DevicePolicyData userPolicy = getUserData(userId);
- return userPolicy.mPermissionPolicy;
- }
+ return mPolicyCache.getPermissionPolicy(userId);
}
@Override
@@ -12733,13 +12726,6 @@
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(callingUserId);
- // TODO (b/137101239): clean up split system user codes
- // ACTION_PROVISION_MANAGED_USER and ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
- // only supported on split-user systems.
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
- return checkManagedUserProvisioningPreCondition(callingUserId);
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
- return checkManagedShareableDeviceProvisioningPreCondition(callingUserId);
}
}
throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -12775,14 +12761,12 @@
}
}
- // TODO (b/137101239): clean up split system user codes
if (isAdb) {
// If shell command runs after user setup completed check device status. Otherwise, OK.
if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
// In non-headless system user mode, DO can be setup only if
// there's no non-system user
if (!mInjector.userManagerIsHeadlessSystemUserMode()
- && !mInjector.userManagerIsSplitSystemUser()
&& mUserManager.getUserCount() > 1) {
return CODE_NONSYSTEM_USER_EXISTS;
}
@@ -12801,16 +12785,13 @@
}
return CODE_OK;
} else {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // In non-split user mode, DO has to be user 0
- if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
- return CODE_NOT_SYSTEM_USER;
- }
- // Only provision DO before setup wizard completes
- // TODO (b/171423186): implement deferred DO setup for headless system user mode
- if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
- return CODE_USER_SETUP_COMPLETED;
- }
+ // DO has to be user 0
+ if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+ return CODE_NOT_SYSTEM_USER;
+ }
+ // Only provision DO before setup wizard completes
+ if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+ return CODE_USER_SETUP_COMPLETED;
}
return CODE_OK;
}
@@ -12831,17 +12812,11 @@
}
}
- // TODO (b/137101239): clean up split system user codes
private int checkManagedProfileProvisioningPreCondition(String packageName,
@UserIdInt int callingUserId) {
if (!hasFeatureManagedUsers()) {
return CODE_MANAGED_USERS_NOT_SUPPORTED;
}
- if (callingUserId == UserHandle.USER_SYSTEM
- && mInjector.userManagerIsSplitSystemUser()) {
- // Managed-profiles cannot be setup on the system user.
- return CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
- }
if (getProfileOwnerAsUser(callingUserId) != null) {
// Managed user cannot have a managed profile.
return CODE_USER_HAS_PROFILE_OWNER;
@@ -12917,37 +12892,6 @@
return null;
}
- // TODO (b/137101239): clean up split system user codes
- private int checkManagedUserProvisioningPreCondition(int callingUserId) {
- if (!hasFeatureManagedUsers()) {
- return CODE_MANAGED_USERS_NOT_SUPPORTED;
- }
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
- return CODE_NOT_SYSTEM_USER_SPLIT;
- }
- if (callingUserId == UserHandle.USER_SYSTEM) {
- // System user cannot be a managed user.
- return CODE_SYSTEM_USER;
- }
- if (hasUserSetupCompleted(callingUserId)) {
- return CODE_USER_SETUP_COMPLETED;
- }
- if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
- return CODE_HAS_PAIRED;
- }
- return CODE_OK;
- }
-
- // TODO (b/137101239): clean up split system user codes
- private int checkManagedShareableDeviceProvisioningPreCondition(int callingUserId) {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
- return CODE_NOT_SYSTEM_USER_SPLIT;
- }
- return checkDeviceOwnerProvisioningPreCondition(callingUserId);
- }
-
private boolean hasFeatureManagedUsers() {
try {
return mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0);
@@ -16006,11 +15950,12 @@
@Override
public UserHandle createAndProvisionManagedProfile(
- @NonNull ManagedProfileProvisioningParams provisioningParams) {
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull String callerPackage) {
final ComponentName admin = provisioningParams.getProfileAdminComponentName();
Objects.requireNonNull(admin, "admin is null");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
@@ -16025,6 +15970,7 @@
"Provisioning preconditions failed with result: " + result);
}
+ final long startTime = SystemClock.elapsedRealtime();
final Set<String> nonRequiredApps = provisioningParams.isLeaveAllSystemAppsEnabled()
? Collections.emptySet()
: mOverlayPackagesProvider.getNonRequiredApps(
@@ -16041,8 +15987,12 @@
"Error creating profile, createProfileForUserEvenWhenDisallowed "
+ "returned null.");
}
-
resetInteractAcrossProfilesAppOps();
+ logEventDuration(
+ DevicePolicyEnums.PLATFORM_PROVISIONING_CREATE_PROFILE_MS,
+ startTime,
+ callerPackage);
+
installExistingAdminPackage(userInfo.id, admin.getPackageName());
if (!enableAdminAndSetProfileOwner(
userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) {
@@ -16052,10 +16002,10 @@
}
setUserSetupComplete(userInfo.id);
- startUser(userInfo.id);
+ startUser(userInfo.id, callerPackage);
maybeMigrateAccount(
userInfo.id, caller.getUserId(), provisioningParams.getAccountToMigrate(),
- provisioningParams.isKeepAccountMigrated());
+ provisioningParams.isKeepAccountMigrated(), callerPackage);
if (provisioningParams.isOrganizationOwnedProvisioning()) {
markIsProfileOwnerOnOrganizationOwnedDevice(admin, userInfo.id);
@@ -16073,7 +16023,11 @@
return userInfo.getUserHandle();
} catch (Exception e) {
- // in case of any errors during provisioning, remove the newly created profile.
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+ .setStrings(callerPackage)
+ .write();
+ // In case of any errors during provisioning, remove the newly created profile.
if (userInfo != null) {
mUserManager.removeUserEvenWhenDisallowed(userInfo.id);
}
@@ -16178,7 +16132,9 @@
mContext.getContentResolver(), USER_SETUP_COMPLETE, 1, userId);
}
- private void startUser(@UserIdInt int userId) throws IllegalStateException {
+ private void startUser(@UserIdInt int userId, String callerPackage)
+ throws IllegalStateException {
+ final long startTime = SystemClock.elapsedRealtime();
final UserUnlockedBlockingReceiver unlockedReceiver = new UserUnlockedBlockingReceiver(
userId);
mContext.registerReceiverAsUser(
@@ -16197,6 +16153,10 @@
throw new ServiceSpecificException(PROVISIONING_RESULT_STARTING_PROFILE_FAILED,
String.format("Timeout whilst waiting for unlock of user %d.", userId));
}
+ logEventDuration(
+ DevicePolicyEnums.PLATFORM_PROVISIONING_START_PROFILE_MS,
+ startTime,
+ callerPackage);
} catch (RemoteException e) {
// Shouldn't happen.
} finally {
@@ -16204,9 +16164,9 @@
}
}
- void maybeMigrateAccount(
+ private void maybeMigrateAccount(
@UserIdInt int targetUserId, @UserIdInt int sourceUserId, Account accountToMigrate,
- boolean keepAccountMigrated) {
+ boolean keepAccountMigrated, String callerPackage) {
final UserHandle sourceUser = UserHandle.of(sourceUserId);
final UserHandle targetUser = UserHandle.of(targetUserId);
if (accountToMigrate == null) {
@@ -16217,13 +16177,16 @@
Slog.w(LOG_TAG, "sourceUser and targetUser are the same, won't migrate account.");
return;
}
- copyAccount(targetUser, sourceUser, accountToMigrate);
+ copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
if (!keepAccountMigrated) {
removeAccount(accountToMigrate);
}
}
- void copyAccount(UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate) {
+ private void copyAccount(
+ UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate,
+ String callerPackage) {
+ final long startTime = SystemClock.elapsedRealtime();
try {
final AccountManager accountManager = mContext.getSystemService(AccountManager.class);
final boolean copySucceeded = accountManager.copyAccountToUser(
@@ -16232,16 +16195,35 @@
targetUser,
/* callback= */ null, /* handler= */ null)
.getResult(60 * 3, TimeUnit.SECONDS);
- if (!copySucceeded) {
+ if (copySucceeded) {
+ logCopyAccountStatus(COPY_ACCOUNT_SUCCEEDED, callerPackage);
+ logEventDuration(
+ DevicePolicyEnums.PLATFORM_PROVISIONING_COPY_ACCOUNT_MS,
+ startTime,
+ callerPackage);
+ } else {
+ logCopyAccountStatus(COPY_ACCOUNT_FAILED, callerPackage);
Slog.e(LOG_TAG, "Failed to copy account to " + targetUser);
}
- } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+ } catch (OperationCanceledException e) {
// Account migration is not considered a critical operation.
+ logCopyAccountStatus(COPY_ACCOUNT_TIMED_OUT, callerPackage);
+ Slog.e(LOG_TAG, "Exception copying account to " + targetUser, e);
+ } catch (AuthenticatorException | IOException e) {
+ logCopyAccountStatus(COPY_ACCOUNT_EXCEPTION, callerPackage);
Slog.e(LOG_TAG, "Exception copying account to " + targetUser, e);
}
}
- void removeAccount(Account account) {
+ private static void logCopyAccountStatus(@CopyAccountStatus int status, String callerPackage) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_COPY_ACCOUNT_STATUS)
+ .setInt(status)
+ .setStrings(callerPackage)
+ .write();
+ }
+
+ private void removeAccount(Account account) {
final AccountManager accountManager =
mContext.getSystemService(AccountManager.class);
final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
@@ -16287,7 +16269,7 @@
@Override
public void provisionFullyManagedDevice(
- FullyManagedDeviceProvisioningParams provisioningParams) {
+ FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
Objects.requireNonNull(deviceAdmin, "admin is null.");
@@ -16299,12 +16281,16 @@
final long identity = Binder.clearCallingIdentity();
try {
- int result = checkProvisioningPreConditionSkipPermission(
- ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
- if (result != CODE_OK) {
- throw new ServiceSpecificException(
- PROVISIONING_RESULT_PRE_CONDITION_FAILED,
- "Provisioning preconditions failed with result: " + result);
+ // TODO(b/178187130): This check fails silent provisioning, uncomment once silent
+ // provisioning is no longer used.
+ if (false) {
+ int result = checkProvisioningPreConditionSkipPermission(
+ ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
+ if (result != CODE_OK) {
+ throw new ServiceSpecificException(
+ PROVISIONING_RESULT_PRE_CONDITION_FAILED,
+ "Provisioning preconditions failed with result: " + result);
+ }
}
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
@@ -16336,6 +16322,12 @@
.setPackage(getManagedProvisioningPackage(mContext))
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+ } catch (Exception e) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+ .setStrings(callerPackage)
+ .write();
+ throw e;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -16412,6 +16404,20 @@
private boolean setActiveAdminAndDeviceOwner(
@UserIdInt int userId, ComponentName adminComponent, String name) {
enableAndSetActiveAdmin(userId, userId, adminComponent);
- return setDeviceOwner(adminComponent, name, userId);
+ // TODO(b/178187130): Directly set DO and remove the check once silent provisioning is no
+ // longer used.
+ if (getDeviceOwnerComponent(/* callingUserOnly= */ true) == null) {
+ return setDeviceOwner(adminComponent, name, userId);
+ }
+ return true;
+ }
+
+ private static void logEventDuration(int eventId, long startTime, String callerPackage) {
+ final long duration = SystemClock.elapsedRealtime() - startTime;
+ DevicePolicyEventLogger
+ .createEvent(eventId)
+ .setTimePeriod(duration)
+ .setStrings(callerPackage)
+ .write();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 22866b4..fc1d831 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -73,25 +73,28 @@
pw.printf(" Prints this help text.\n\n");
pw.printf(" %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION);
pw.printf(" Checks if the give operation is safe \n\n");
- pw.printf(" %s <OPERATION_ID> <true|false>\n", CMD_SET_SAFE_OPERATION);
+ pw.printf(" %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION);
pw.printf(" Emulates the result of the next call to check if the given operation is safe"
+ " \n\n");
}
private int runIsSafeOperation(PrintWriter pw) {
int operation = Integer.parseInt(getNextArgRequired());
- boolean safe = mService.canExecute(operation);
- pw.printf("Operation %s is %s\n", DevicePolicyManager.operationToString(operation),
- safe ? "SAFE" : "UNSAFE");
+ int reason = mService.getUnsafeOperationReason(operation);
+ boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+ pw.printf("Operation %s is %b. Reason: %s\n",
+ DevicePolicyManager.operationToString(operation), safe,
+ DevicePolicyManager.unsafeOperationReasonToString(reason));
return 0;
}
private int runSetSafeOperation(PrintWriter pw) {
int operation = Integer.parseInt(getNextArgRequired());
- boolean safe = getNextArg().equals("true");
- mService.setNextOperationSafety(operation, safe);
+ int reason = Integer.parseInt(getNextArgRequired());
+ mService.setNextOperationSafety(operation, reason);
pw.printf("Next call to check operation %s will return %s\n",
- DevicePolicyManager.operationToString(operation), safe ? "SAFE" : "UNSAFE");
+ DevicePolicyManager.operationToString(operation),
+ DevicePolicyManager.unsafeOperationReasonToString(reason));
return 0;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
index f7a8261..883f95d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
@@ -15,9 +15,12 @@
*/
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
import static android.app.admin.DevicePolicyManager.operationToString;
+import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
import android.app.admin.DevicePolicySafetyChecker;
import android.util.Slog;
@@ -40,31 +43,33 @@
private final DevicePolicyManagerService mService;
private final DevicePolicySafetyChecker mRealSafetyChecker;
private final @DevicePolicyOperation int mOperation;
- private final boolean mSafe;
+ private final @UnsafeOperationReason int mReason;
OneTimeSafetyChecker(DevicePolicyManagerService service,
- @DevicePolicyOperation int operation, boolean safe) {
+ @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) {
mService = Objects.requireNonNull(service);
mOperation = operation;
- mSafe = safe;
+ mReason = reason;
mRealSafetyChecker = service.getDevicePolicySafetyChecker();
Slog.i(TAG, "Saving real DevicePolicySafetyChecker as " + mRealSafetyChecker);
}
@Override
- public boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) {
+ @UnsafeOperationReason
+ public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
String name = operationToString(operation);
- boolean safe = true;
+ int reason = UNSAFE_OPERATION_REASON_NONE;
if (operation == mOperation) {
- safe = mSafe;
+ reason = mReason;
} else {
Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name
+ ", should be " + operationToString(mOperation));
}
- Slog.i(TAG, "isDevicePolicyOperationSafe(" + name + "): returning " + safe
+ Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning "
+ + unsafeOperationReasonToString(reason)
+ " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
- return safe;
+ return reason;
}
@Override
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index b2efc71..42360d8 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -88,7 +88,6 @@
}
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
- ps->giveThreadPoolName();
// sm->addService increments the reference count, and now we're OK with returning the pointer.
return self.get();
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 132f973..56cb3d1 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1091,7 +1091,7 @@
maxPendingTimeUs = std::max(maxPendingTimeUs, microseconds(timeouts.maxPendingTimeUs));
}
if (maxPendingTimeUs < Constants::minPerUidTimeout) {
- LOG(ERROR) << "Skip setting timeouts: maxPendingTime < Constants::minPerUidTimeout"
+ LOG(ERROR) << "Skip setting read timeouts (maxPendingTime < Constants::minPerUidTimeout): "
<< duration_cast<milliseconds>(maxPendingTimeUs).count() << "ms < "
<< Constants::minPerUidTimeout.count() << "ms";
return;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 3573177..25d3f77 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -221,10 +221,6 @@
timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
}
- LOG(ERROR) << "Set read timeouts: " << timeouts.size() << " ["
- << (timeouts.empty() ? -1 : timeouts.front().uid) << "@"
- << (timeouts.empty() ? -1 : timeouts.front().minTimeUs / 1000) << "ms]";
-
return incfs::setUidReadTimeouts(control, timeouts);
}
};
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5ced868..50cb00f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -160,6 +160,7 @@
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.dex.SystemServerDexLoadReporter;
+import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.policy.PermissionPolicyService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.policy.role.RoleServicePlatformHelperImpl;
@@ -342,6 +343,8 @@
"com.android.server.contentsuggestions.ContentSuggestionsManagerService";
private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
"com.android.server.searchui.SearchUiManagerService";
+ private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
+ "com.android.server.smartspace.SmartspaceManagerService";
private static final String DEVICE_IDLE_CONTROLLER_CLASS =
"com.android.server.DeviceIdleController";
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -365,7 +368,7 @@
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
private static final String GAME_MANAGER_SERVICE_CLASS =
- "com.android.server.graphics.GameManagerService$Lifecycle";
+ "com.android.server.app.GameManagerService$Lifecycle";
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
@@ -1058,11 +1061,18 @@
SystemClock.elapsedRealtime());
}
+ t.traceBegin("StartDomainVerificationService");
+ DomainVerificationService domainVerificationService = new DomainVerificationService(
+ mSystemContext, SystemConfig.getInstance(), platformCompat);
+ mSystemServiceManager.startService(domainVerificationService);
+ t.traceEnd();
+
t.traceBegin("StartPackageManagerService");
try {
Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
- mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
+ domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,
+ mOnlyCore);
} finally {
Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
}
@@ -1342,6 +1352,13 @@
mSystemServiceManager.startService(DropBoxManagerService.class);
t.traceEnd();
+ // Grants default permissions and defines roles
+ t.traceBegin("StartRoleManagerService");
+ LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
+ new RoleServicePlatformHelperImpl(mSystemContext));
+ mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("StartVibratorManagerService");
mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
t.traceEnd();
@@ -1676,6 +1693,12 @@
mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
t.traceEnd();
+ // Smartspace manager service
+ // TODO: add deviceHasConfigString(context, R.string.config_defaultSmartspaceService)
+ t.traceBegin("StartSmartspaceService");
+ mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("InitConnectivityModuleConnector");
try {
ConnectivityModuleConnector.getInstance().init(context);
@@ -2052,13 +2075,6 @@
t.traceEnd();
}
- // Grants default permissions and defines roles
- t.traceBegin("StartRoleManagerService");
- LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
- new RoleServicePlatformHelperImpl(mSystemContext));
- mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
- t.traceEnd();
-
// We need to always start this service, regardless of whether the
// FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
// of initializing various settings. It will internally modify its behavior
diff --git a/services/smartspace/Android.bp b/services/smartspace/Android.bp
new file mode 100644
index 0000000..fcf780d
--- /dev/null
+++ b/services/smartspace/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.smartspace-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.smartspace",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.smartspace-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/smartspace/OWNERS b/services/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/services/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
new file mode 100644
index 0000000..3b5a5a5
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
@@ -0,0 +1,112 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.smartspace.ISmartspaceService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.smartspace.SmartspaceService} implementation in another
+ * process.
+ */
+public class RemoteSmartspaceService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteSmartspaceService,
+ ISmartspaceService> {
+
+ private static final String TAG = "RemoteSmartspaceService";
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ private final RemoteSmartspaceServiceCallbacks mCallback;
+
+ public RemoteSmartspaceService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteSmartspaceServiceCallbacks callback, boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, serviceInterface, componentName, userId, callback,
+ context.getMainThreadHandler(),
+ bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+ verbose, /* initialCapacity= */ 1);
+ mCallback = callback;
+ }
+
+ @Override
+ protected ISmartspaceService getServiceInterface(IBinder service) {
+ return ISmartspaceService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Schedules a request to bind to the remote service.
+ */
+ public void reconnect() {
+ super.scheduleBind();
+ }
+
+ /**
+ * Schedule async request on remote service.
+ */
+ public void scheduleOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
+ * Execute async request on remote service immediately instead of sending it to Handler queue.
+ */
+ public void executeOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+ executeAsyncRequest(request);
+ }
+
+ /**
+ * Failure callback
+ */
+ public interface RemoteSmartspaceServiceCallbacks
+ extends VultureCallback<RemoteSmartspaceService> {
+
+ /**
+ * Notifies a the failure or timeout of a remote call.
+ */
+ void onFailureOrTimeout(boolean timedOut);
+
+ /**
+ * Notifies change in connected state of the remote service.
+ */
+ void onConnectedStateChanged(boolean connected);
+ }
+
+ @Override // from AbstractRemoteService
+ protected void handleOnConnectedStateChanged(boolean connected) {
+ if (mCallback != null) {
+ mCallback.onConnectedStateChanged(connected);
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
new file mode 100644
index 0000000..169b85e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.smartspace;
+
+import static android.Manifest.permission.MANAGE_SMARTSPACE;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.SMARTSPACE_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.ISmartspaceManager;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return smartspace targets given a query.
+ */
+public class SmartspaceManagerService extends
+ AbstractMasterSystemService<SmartspaceManagerService, SmartspacePerUserService> {
+
+ private static final String TAG = SmartspaceManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public SmartspaceManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultSmartspaceService), null,
+ PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected SmartspacePerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new SmartspacePerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(SMARTSPACE_SERVICE, new SmartspaceManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_SMARTSPACE, TAG);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageUpdatedLocked();
+ }
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageRestartedLocked();
+ }
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class SmartspaceManagerStub extends ISmartspaceManager.Stub {
+
+ @Override
+ public void createSmartspaceSession(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ runForUserLocked("createSmartspaceSession", sessionId, (service) ->
+ service.onCreateSmartspaceSessionLocked(smartspaceConfig, sessionId, token));
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+ SmartspaceTargetEvent event) {
+ runForUserLocked("notifySmartspaceEvent", sessionId,
+ (service) -> service.notifySmartspaceEventLocked(sessionId, event));
+ }
+
+ @Override
+ public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+ runForUserLocked("requestSmartspaceUpdate", sessionId,
+ (service) -> service.requestSmartspaceUpdateLocked(sessionId));
+ }
+
+ @Override
+ public void registerSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ runForUserLocked("registerSmartspaceUpdates", sessionId,
+ (service) -> service.registerSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ runForUserLocked("unregisterSmartspaceUpdates", sessionId,
+ (service) -> service.unregisterSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void destroySmartspaceSession(@NonNull SmartspaceSessionId sessionId) {
+ runForUserLocked("destroySmartspaceSession", sessionId,
+ (service) -> service.onDestroyLocked(sessionId));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ new SmartspaceManagerServiceShellCommand(SmartspaceManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private void runForUserLocked(@NonNull final String func,
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final Consumer<SmartspacePerUserService> c) {
+ ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+ final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ sessionId.getUserId(), false, ALLOW_NON_FULL, null, null);
+
+ if (DEBUG) {
+ Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ }
+ if (!(mServiceNameResolver.isTemporary(userId)
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+
+ String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final SmartspacePerUserService service = getServiceForUserLocked(userId);
+ c.accept(service);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
new file mode 100644
index 0000000..4143418e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the SmartspaceManagerService.
+ */
+public class SmartspaceManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ SmartspaceManagerServiceShellCommand.class.getSimpleName();
+
+ private final SmartspaceManagerService mService;
+
+ public SmartspaceManagerServiceShellCommand(@NonNull SmartspaceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ pw.println("SmartspaceService temporarily reset. ");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("SmartspaceService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("SmartspaceManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
new file mode 100644
index 0000000..db43468
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
@@ -0,0 +1,411 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService;
+import android.service.smartspace.SmartspaceService;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link SmartspaceManagerService}.
+ */
+public class SmartspacePerUserService extends
+ AbstractPerUserSystemService<SmartspacePerUserService, SmartspaceManagerService>
+ implements RemoteSmartspaceService.RemoteSmartspaceServiceCallbacks {
+
+ private static final String TAG = SmartspacePerUserService.class.getSimpleName();
+ @GuardedBy("mLock")
+ private final ArrayMap<SmartspaceSessionId, SmartspaceSessionInfo> mSessionInfos =
+ new ArrayMap<>();
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteSmartspaceService mRemoteService;
+ /**
+ * When {@code true}, remote service died but service state is kept so it's restored after
+ * the system re-binds to it.
+ */
+ @GuardedBy("mLock")
+ private boolean mZombie;
+
+ protected SmartspacePerUserService(SmartspaceManagerService master,
+ Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new NameNotFoundException("Could not get service for " + serviceComponent);
+ }
+ // TODO(b/177858728): must check that either the service is from a system component,
+ // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+ // OEMs are implementing the real service and also verify the proper permissions
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (!isEnabledLocked()) {
+ // Clear the remote service for the next call
+ updateRemoteServiceLocked();
+ }
+ }
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onCreateSmartspaceSessionLocked(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.onCreateSmartspaceSession(smartspaceConfig, sessionId));
+
+ if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+ final SmartspaceSessionInfo sessionInfo = new SmartspaceSessionInfo(
+ sessionId, smartspaceConfig, token, () -> {
+ synchronized (mLock) {
+ onDestroyLocked(sessionId);
+ }
+ });
+ if (sessionInfo.linkToDeath()) {
+ mSessionInfos.put(sessionId, sessionInfo);
+ } else {
+ // destroy the session if calling process is already dead
+ onDestroyLocked(sessionId);
+ }
+ }
+ }
+
+ /**
+ * Records an smartspace event to the service.
+ */
+ @GuardedBy("mLock")
+ public void notifySmartspaceEventLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull SmartspaceTargetEvent event) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.notifySmartspaceEvent(sessionId, event));
+ }
+
+ /**
+ * Requests the service to return smartspace results of an input query.
+ */
+ @GuardedBy("mLock")
+ public void requestSmartspaceUpdateLocked(@NonNull SmartspaceSessionId sessionId) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId,
+ s -> s.requestSmartspaceUpdate(sessionId));
+ }
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void registerSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.registerSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.addCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void unregisterSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.unregisterSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.removeCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Notifies the service of the end of an existing smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onDestroyLocked(@NonNull SmartspaceSessionId sessionId) {
+ if (isDebug()) {
+ Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
+ }
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.onDestroySmartspaceSession(sessionId));
+ sessionInfo.destroy();
+ }
+
+ @Override
+ public void onFailureOrTimeout(boolean timedOut) {
+ if (isDebug()) {
+ Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+ }
+ // Do nothing, we are just proxying to the smartspace ui service
+ }
+
+ @Override
+ public void onConnectedStateChanged(boolean connected) {
+ if (isDebug()) {
+ Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
+ }
+ if (connected) {
+ synchronized (mLock) {
+ if (mZombie) {
+ // Validation check - shouldn't happen
+ if (mRemoteService == null) {
+ Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
+ return;
+ }
+ mZombie = false;
+ resurrectSessionsLocked();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDied(RemoteSmartspaceService service) {
+ if (isDebug()) {
+ Slog.w(TAG, "onServiceDied(): service=" + service);
+ }
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ updateRemoteServiceLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void updateRemoteServiceLocked() {
+ if (mRemoteService != null) {
+ mRemoteService.destroy();
+ mRemoteService = null;
+ }
+ }
+
+ void onPackageUpdatedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageUpdatedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ void onPackageRestartedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageRestartedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ private void destroyAndRebindRemoteService() {
+ if (mRemoteService == null) {
+ return;
+ }
+
+ if (isDebug()) {
+ Slog.d(TAG, "Destroying the old remote service.");
+ }
+ mRemoteService.destroy();
+ mRemoteService = null;
+
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ mRemoteService = getRemoteServiceLocked();
+ if (mRemoteService != null) {
+ if (isDebug()) {
+ Slog.d(TAG, "Rebinding to the new remote service.");
+ }
+ mRemoteService.reconnect();
+ }
+ }
+
+ /**
+ * Called after the remote service connected, it's used to restore state from a 'zombie'
+ * service (i.e., after it died).
+ */
+ private void resurrectSessionsLocked() {
+ final int numSessions = mSessionInfos.size();
+ if (isDebug()) {
+ Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
+ + numSessions + " sessions.");
+ }
+
+ for (SmartspaceSessionInfo sessionInfo : mSessionInfos.values()) {
+ sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ protected boolean resolveService(
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb) {
+
+ final RemoteSmartspaceService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.executeOnResolvedService(cb);
+ }
+ return service != null;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteSmartspaceService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteSmartspaceService(getContext(),
+ SmartspaceService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ private static final class SmartspaceSessionInfo {
+ private static final boolean DEBUG = false; // Do not submit with true
+ @NonNull
+ final IBinder mToken;
+ @NonNull
+ final IBinder.DeathRecipient mDeathRecipient;
+ @NonNull
+ private final SmartspaceSessionId mSessionId;
+ @NonNull
+ private final SmartspaceConfig mSmartspaceConfig;
+ private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
+ new RemoteCallbackList<ISmartspaceCallback>() {
+ @Override
+ public void onCallbackDied(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Binder died for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ if (mCallbacks.getRegisteredCallbackCount() == 0) {
+ destroy();
+ }
+ }
+ };
+
+ SmartspaceSessionInfo(
+ @NonNull final SmartspaceSessionId id,
+ @NonNull final SmartspaceConfig context,
+ @NonNull final IBinder token,
+ @NonNull final IBinder.DeathRecipient deathRecipient) {
+ if (DEBUG) {
+ Slog.d(TAG, "Creating SmartspaceSessionInfo for session Id=" + id);
+ }
+ mSessionId = id;
+ mSmartspaceConfig = context;
+ mToken = token;
+ mDeathRecipient = deathRecipient;
+ }
+
+ void addCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.register(callback);
+ }
+
+ void removeCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.unregister(callback);
+ }
+
+ boolean linkToDeath() {
+ try {
+ mToken.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+ + mSessionId);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ void destroy() {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
+ + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
+ }
+ if (mToken != null) {
+ mToken.unlinkToDeath(mDeathRecipient, 0);
+ }
+ mCallbacks.kill();
+ }
+
+ void resurrectSessionLocked(SmartspacePerUserService service, IBinder token) {
+ int callbackCount = mCallbacks.getRegisteredCallbackCount();
+ if (DEBUG) {
+ Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
+ + ") for session Id=" + mSessionId + " and "
+ + callbackCount + " callbacks.");
+ }
+ service.onCreateSmartspaceSessionLocked(mSmartspaceConfig, mSessionId, token);
+ }
+ }
+}
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 21c863d..6c5c1d4 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -54,6 +54,7 @@
import org.mockito.Mockito.verify
import org.testng.Assert.assertThrows
import java.io.File
+import java.util.UUID
@RunWith(Parameterized::class)
class PackageManagerComponentLabelIconOverrideTest {
@@ -262,8 +263,13 @@
.apply(block)
.hideAsFinal()
- private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"),
- null, null, null, null, 0, 0, 0, 0, null, null, null)) {
+ private fun makePkgSetting(pkgName: String) = spy(
+ PackageSetting(
+ pkgName, null, File("/test"),
+ null, null, null, null, 0, 0, 0, 0, null, null, null,
+ UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
+ )
+ ) {
this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
}
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
new file mode 100644
index 0000000..4aa8abc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -0,0 +1,29 @@
+// 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.
+
+android_test {
+ name: "PackageManagerServiceUnitTests",
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "junit",
+ "services.core",
+ "servicestests-utils",
+ "testng",
+ "truth-prebuilt",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..2ef7a1f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test">
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.pm.test"
+ />
+
+</manifest>
+
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml
new file mode 100644
index 0000000..78dd1c5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Test module config for PackageManagerServiceUnitTests">
+ <option name="test-tag" value="PackageManagerServiceUnitTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PackageManagerServiceUnitTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.server.pm.test" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
new file mode 100644
index 0000000..e99b071
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.verify.domain
+
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Build
+import android.os.PatternMatcher
+import android.util.ArraySet
+import com.android.server.SystemConfig
+import com.android.server.compat.PlatformCompat
+import com.android.server.pm.verify.domain.DomainVerificationCollector
+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
+import org.junit.Test
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+
+class DomainVerificationCollectorTest {
+
+ companion object {
+ private const val TEST_PKG_NAME = "com.test.pkg"
+ }
+
+ private val platformCompat: PlatformCompat = mockThrowOnUnmocked {
+ whenever(isChangeEnabled(eq(DomainVerificationCollector.RESTRICT_DOMAINS), any())) {
+ (arguments[1] as ApplicationInfo).targetSdkVersion >= Build.VERSION_CODES.S
+ }
+ }
+
+ @Test
+ fun verifyV1() {
+ val pkg = mockPkg(useV2 = false, autoVerify = true)
+ val collector = mockCollector()
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com", "example4.com")
+ }
+
+ @Test
+ fun verifyV1NoAutoVerify() {
+ val pkg = mockPkg(useV2 = false, autoVerify = false)
+ val collector = mockCollector()
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ @Test
+ fun verifyV1ForceAutoVerify() {
+ val pkg = mockPkg(useV2 = false, autoVerify = false)
+ val collector = mockCollector(linkedApps = setOf(TEST_PKG_NAME))
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com", "example4.com")
+ }
+
+ @Test
+ fun verifyV1NoValidIntentFilter() {
+ val pkg = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { TEST_PKG_NAME }
+ whenever(targetSdkVersion) { Build.VERSION_CODES.R }
+
+ val activityList = listOf(
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example1.com", null)
+ }
+ )
+ },
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(true)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+
+ // The presence of a non-web-scheme as the only autoVerify
+ // intent-filter, when non-forced, means that v1 will not pick
+ // up the package for verification.
+ addDataScheme("nonWebScheme")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example2.com", null)
+ }
+ )
+ },
+ )
+
+ whenever(activities) { activityList }
+ }
+
+ val collector = mockCollector()
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ @Test
+ fun verifyV2() {
+ val pkg = mockPkg(useV2 = true, autoVerify = true)
+ val collector = mockCollector()
+
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg))
+ .containsExactly("example1.com", "example3.com")
+ }
+
+ @Test
+ fun verifyV2NoAutoVerify() {
+ val pkg = mockPkg(useV2 = true, autoVerify = false)
+ val collector = mockCollector()
+
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ @Test
+ fun verifyV2ForceAutoVerifyIgnored() {
+ val pkg = mockPkg(useV2 = true, autoVerify = false)
+ val collector = mockCollector(linkedApps = setOf(TEST_PKG_NAME))
+
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ private fun mockCollector(linkedApps: Set<String> = emptySet()): DomainVerificationCollector {
+ val systemConfig = mockThrowOnUnmocked<SystemConfig> {
+ whenever(this.linkedApps) { ArraySet(linkedApps) }
+ }
+
+ return DomainVerificationCollector(platformCompat, systemConfig)
+ }
+
+ private fun mockPkg(useV2: Boolean, autoVerify: Boolean): AndroidPackage {
+ // Translate equivalent of the following manifest declaration. This string isn't actually
+ // parsed, but it's a far easier to read representation of the test data.
+ // language=XML
+ """
+ <xml>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="http"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub"/>
+ <data android:host="example1.com"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="http"/>
+ <data android:path="/sub2"/>
+ <data android:host="example2.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub3"/>
+ <data android:host="example3.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub4"/>
+ <data android:host="example4.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub5"/>
+ <data android:host="example5.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub5"/>
+ <data android:host="example5.com"/>
+ </intent-filter>
+ </xml>
+ """.trimIndent()
+
+ return mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { TEST_PKG_NAME }
+ whenever(targetSdkVersion) {
+ if (useV2) Build.VERSION_CODES.S else Build.VERSION_CODES.R
+ }
+
+ // The intents are split into separate Activities to test that multiple are collected
+ val activityList = listOf(
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example1.com", null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example2.com", null)
+ }
+ )
+ },
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataPath("/sub3", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example3.com", null)
+ }
+ )
+ },
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addDataScheme("https")
+ addDataPath("/sub4", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example4.com", null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataPath("/sub5", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example5.com", null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataPath("/sub6", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example6.com", null)
+ }
+ )
+ },
+ )
+
+ whenever(activities) { activityList }
+ }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
new file mode 100644
index 0000000..deb3147
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.verify.domain
+
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.UserHandle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.UUID
+
+@RunWith(Parameterized::class)
+class DomainVerificationCoreApiTest {
+
+ companion object {
+ private val IS_EQUAL_TO: (value: Any, other: Any) -> Unit = { value, other ->
+ assertThat(value).isEqualTo(other)
+ }
+ private val IS_MAP_EQUAL_TO: (value: Map<*, *>, other: Map<*, *>) -> Unit = { value,
+ other ->
+ assertThat(value).containsExactlyEntriesIn(other)
+ }
+
+ @JvmStatic
+ @Parameterized.Parameters
+ fun parameters() = arrayOf(
+ Parameter(
+ initial = {
+ DomainVerificationRequest(
+ setOf(
+ "com.test.pkg.one",
+ "com.test.pkg.two"
+ )
+ )
+ },
+ unparcel = { DomainVerificationRequest.CREATOR.createFromParcel(it) },
+ assertion = { first, second ->
+ assertAll<DomainVerificationRequest, Set<String>>(first, second,
+ { it.packageNames }, { it.component1() }) { value, other ->
+ assertThat(value).containsExactlyElementsIn(other)
+ }
+ }
+ ),
+ Parameter(
+ initial = {
+ DomainVerificationInfo(
+ UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
+ "com.test.pkg",
+ mapOf(
+ "example.com" to 0,
+ "example.org" to 1,
+ "example.new" to 1000
+ )
+ )
+ },
+ unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) },
+ assertion = { first, second ->
+ assertAll<DomainVerificationInfo, UUID>(first, second,
+ { it.identifier }, { it.component1() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationInfo, String>(first, second,
+ { it.packageName }, { it.component2() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationInfo, Map<String, Int?>>(first, second,
+ { it.hostToStateMap }, { it.component3() }, IS_MAP_EQUAL_TO
+ )
+ }
+ ),
+ Parameter(
+ initial = {
+ DomainVerificationUserSelection(
+ UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
+ "com.test.pkg",
+ UserHandle.of(10),
+ true,
+ mapOf(
+ "example.com" to true,
+ "example.org" to false,
+ "example.new" to true
+ )
+ )
+ },
+ unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) },
+ assertion = { first, second ->
+ assertAll<DomainVerificationUserSelection, UUID>(first, second,
+ { it.identifier }, { it.component1() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, String>(first, second,
+ { it.packageName }, { it.component2() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, UserHandle>(first, second,
+ { it.user }, { it.component3() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, Boolean>(
+ first, second, { it.isLinkHandlingAllowed },
+ { it.component4() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, Map<String, Boolean>>(
+ first, second, { it.hostToUserSelectionMap },
+ { it.component5() }, IS_MAP_EQUAL_TO
+ )
+ }
+ )
+ )
+
+ class Parameter<T : Parcelable>(
+ val initial: () -> T,
+ val unparcel: (Parcel) -> T,
+ private val assertion: (first: T, second: T) -> Unit
+ ) {
+ @Suppress("UNCHECKED_CAST")
+ fun assert(first: Any, second: Any) = assertion(first as T, second as T)
+ }
+
+ private fun <T> assertAll(vararg values: T, block: (value: T, other: T) -> Unit) {
+ values.indices.drop(1).forEach {
+ block(values[0], values[it])
+ }
+ }
+
+ private fun <T, V : Any> assertAll(
+ first: T,
+ second: T,
+ fieldValue: (T) -> V,
+ componentValue: (T) -> V,
+ assertion: (value: V, other: V) -> Unit
+ ) {
+ val values = arrayOf<Any>(fieldValue(first), fieldValue(second),
+ componentValue(first), componentValue(second))
+ values.indices.drop(1).forEach {
+ @Suppress("UNCHECKED_CAST")
+ assertion(values[0] as V, values[it] as V)
+ }
+ }
+ }
+
+ @Parameterized.Parameter(0)
+ lateinit var parameter: Parameter<*>
+
+ @Test
+ fun parcel() {
+ val parcel = Parcel.obtain()
+ val initial = parameter.initial()
+ initial.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ val newInitial = parameter.initial()
+ val unparceled = parameter.unparcel(parcel)
+ parameter.assert(newInitial, unparceled)
+
+ assertAll(initial, newInitial, unparceled) { value: Any, other: Any ->
+ assertThat(value).isEqualTo(other)
+ }
+ }
+}
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
new file mode 100644
index 0000000..d863194
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -0,0 +1,478 @@
+/*
+ * 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.test.verify.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageUserState
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+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
+import com.android.server.pm.verify.domain.DomainVerificationEnforcer
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.pm.verify.domain.DomainVerificationService
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.spyThrowOnUnmocked
+import com.android.server.testutils.whenever
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+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 {
+
+ val context: Context = InstrumentationRegistry.getInstrumentation().context
+
+ companion object {
+ private val INTERNAL_UIDS = listOf(Process.ROOT_UID, Process.SHELL_UID, Process.SYSTEM_UID)
+ 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"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun parameters(): Array<Any> {
+ 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)
+ }
+ )
+ }
+ )
+ }
+ }
+
+ 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()
+ }
+ }
+ }
+
+ val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, DomainVerificationService> =
+ {
+ 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())
+ })
+ }
+ )
+ }
+
+ 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)
+ }
+
+ 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)
+ }
+
+ return arrayOf(
+ enforcer(Type.INTERNAL, "internal") { callingUid, _, _, _ ->
+ assertInternal(callingUid)
+ },
+ enforcer(Type.QUERENT, "approvedQuerent") { callingUid, _, _, proxy ->
+ assertApprovedQuerent(callingUid, proxy)
+ },
+ enforcer(Type.VERIFIER, "approvedVerifier") { callingUid, _, _, proxy ->
+ assertApprovedVerifier(callingUid, proxy)
+ },
+ enforcer(
+ Type.SELECTOR,
+ "approvedUserSelector"
+ ) { callingUid, callingUserId, userId, _ ->
+ assertApprovedUserSelector(callingUid, callingUserId, userId)
+ },
+
+ service(Type.INTERNAL, "setStatusInternalPackageName") { _, _, _ ->
+ setDomainVerificationStatusInternal(
+ TEST_PKG,
+ DomainVerificationManager.STATE_SUCCESS,
+ ArraySet(setOf("example.com"))
+ )
+ },
+ service(Type.INTERNAL, "setUserSelectionInternal") { _, _, userId ->
+ setDomainVerificationUserSelectionInternal(
+ userId,
+ TEST_PKG,
+ false,
+ ArraySet(setOf("example.com"))
+ )
+ },
+ service(Type.INTERNAL, "verifyPackages") { _, _, _ ->
+ verifyPackages(listOf(TEST_PKG), true)
+ },
+ service(Type.INTERNAL, "clearState") { _, _, _ ->
+ clearDomainVerificationState(listOf(TEST_PKG))
+ },
+ service(Type.INTERNAL, "clearUserSelections") { _, _, userId ->
+ clearUserSelections(listOf(TEST_PKG), userId)
+ },
+ service(Type.VERIFIER, "getPackageNames") { _, _, _ ->
+ validVerificationPackageNames
+ },
+ service(Type.QUERENT, "getInfo") { _, _, _ ->
+ getDomainVerificationInfo(TEST_PKG)
+ },
+ service(Type.VERIFIER, "setStatus") { _, _, _ ->
+ setDomainVerificationStatus(
+ uuid,
+ setOf("example.com"),
+ DomainVerificationManager.STATE_SUCCESS
+ )
+ },
+ service(Type.VERIFIER, "setStatusInternalUid") { callingUid, _, _ ->
+ setDomainVerificationStatusInternal(
+ callingUid,
+ uuid,
+ setOf("example.com"),
+ DomainVerificationManager.STATE_SUCCESS
+ )
+ },
+ service(Type.SELECTOR, "setLinkHandlingAllowed") { _, _, _ ->
+ setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
+ },
+ service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { _, _, userId ->
+ setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, userId)
+ },
+ service(Type.SELECTOR, "getUserSelection") { _, _, _ ->
+ getDomainVerificationUserSelection(TEST_PKG)
+ },
+ service(Type.SELECTOR_USER, "getUserSelectionUserId") { _, _, userId ->
+ getDomainVerificationUserSelection(TEST_PKG, userId)
+ },
+ service(Type.SELECTOR, "setUserSelection") { _, _, _ ->
+ setDomainVerificationUserSelection(uuid, setOf("example.com"), true)
+ },
+ service(Type.SELECTOR_USER, "setUserSelectionUserId") { _, _, userId ->
+ setDomainVerificationUserSelection(uuid, setOf("example.com"), true, userId)
+ },
+ )
+ }
+
+ data class Params<T : Any>(
+ val type: Type,
+ val construct: (context: Context) -> T,
+ val name: String,
+ private val method: (
+ T, callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
+ ) -> Unit
+ ) {
+ override fun toString() = "${type}_$name"
+
+ fun runMethod(
+ target: Any,
+ callingUid: Int,
+ callingUserId: Int,
+ userId: Int,
+ proxy: DomainVerificationProxy
+ ) {
+ @Suppress("UNCHECKED_CAST")
+ method(target as T, callingUid, callingUserId, userId, proxy)
+ }
+ }
+ }
+
+ @Parameterized.Parameter(0)
+ lateinit var params: Params<*>
+
+ private val proxy: DomainVerificationProxy = mockThrowOnUnmocked {
+ whenever(isCallerVerifier(VERIFIER_UID)) { true }
+ whenever(isCallerVerifier(NON_VERIFIER_UID)) { false }
+ whenever(sendBroadcastForPackages(any()))
+ }
+
+ @Test
+ fun verify() {
+ when (params.type) {
+ Type.INTERNAL -> internal()
+ Type.QUERENT -> approvedQuerent()
+ Type.VERIFIER -> approvedVerifier()
+ Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
+ Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
+ }.run { /*exhaust*/ }
+ }
+
+ fun internal() {
+ 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) }
+ }
+
+ fun approvedQuerent() {
+ val allowUserSelection = 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()
+ }
+ }
+ }
+ val target = params.construct(context)
+
+ INTERNAL_UIDS.forEach { runMethod(target, it) }
+
+ verifyNoMoreInteractions(context)
+
+ runMethod(target, VERIFIER_UID)
+ assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+
+ allowUserSelection.set(true)
+
+ runMethod(target, NON_VERIFIER_UID)
+ }
+
+ fun approvedVerifier() {
+ val shouldThrow = AtomicBoolean(false)
+ val context: Context = mockThrowOnUnmocked {
+ whenever(
+ enforcePermission(
+ eq(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT),
+ anyInt(), anyInt(), anyString()
+ )
+ ) {
+ if (shouldThrow.get()) {
+ throw SecurityException()
+ }
+ }
+ }
+ val target = params.construct(context)
+
+ INTERNAL_UIDS.forEach { runMethod(target, it) }
+
+ verifyNoMoreInteractions(context)
+
+ runMethod(target, VERIFIER_UID)
+ assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+
+ shouldThrow.set(true)
+
+ assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
+ assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+ }
+
+ fun approvedUserSelector(verifyCrossUser: Boolean) {
+ val allowUserSelection = AtomicBoolean(true)
+ val allowInteractAcrossUsers = AtomicBoolean(true)
+ 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()
+ }
+ }
+ }
+ 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) }
+ }
+
+ val callingUserId = 0
+ val notCallingUserId = 1
+
+ runEachTestCaseWrapped(callingUserId, callingUserId)
+ if (verifyCrossUser) {
+ runEachTestCaseWrapped(callingUserId, notCallingUserId)
+ }
+
+ allowInteractAcrossUsers.set(false)
+
+ runEachTestCaseWrapped(callingUserId, callingUserId)
+
+ 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)
+ }
+ }
+
+ allowInteractAcrossUsers.set(true)
+
+ runEachTestCaseWrapped(callingUserId, callingUserId) {
+ assertThrows(SecurityException::class.java, it)
+ }
+ if (verifyCrossUser) {
+ runEachTestCaseWrapped(callingUserId, notCallingUserId) {
+ assertThrows(SecurityException::class.java, it)
+ }
+ }
+ }
+
+ private fun runMethod(target: Any, callingUid: Int, callingUserId: Int = 0, userId: Int = 0) {
+ params.runMethod(target, callingUid, callingUserId, userId, proxy)
+ }
+
+ enum class Type {
+ // System/shell only
+ INTERNAL,
+
+ // INTERNAL || domain verification agent || user setting permission holder
+ QUERENT,
+
+ // INTERNAL || domain verification agent
+ VERIFIER,
+
+ // Holding the user setting permission
+ SELECTOR,
+
+ // Holding the user setting permission, but targeting cross user
+ SELECTOR_USER
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
new file mode 100644
index 0000000..9a3bd99
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.verify.domain
+
+import android.content.pm.IntentFilterVerificationInfo
+import android.content.pm.PackageManager
+import android.util.ArraySet
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings
+import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.readXml
+import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.writeXml
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class DomainVerificationLegacySettingsTest {
+
+ @Rule
+ @JvmField
+ val tempFolder = TemporaryFolder()
+
+ @Test
+ fun writeAndReadBackNormal() {
+ val settings = DomainVerificationLegacySettings().apply {
+ add(
+ "com.test.one",
+ IntentFilterVerificationInfo(
+ "com.test.one",
+ ArraySet(setOf("example1.com", "example2.com"))
+ ).apply {
+ status = PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK
+ }
+ )
+ add(
+ "com.test.one",
+ 0, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ )
+ add(
+ "com.test.one",
+ 10, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER
+ )
+
+ add(
+ "com.test.two",
+ IntentFilterVerificationInfo(
+ "com.test.two",
+ ArraySet(setOf("example3.com"))
+ )
+ )
+
+ add(
+ "com.test.three",
+ 11, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ )
+ }
+
+
+ val file = tempFolder.newFile().writeXml(settings::writeSettings)
+ val newSettings = file.readXml {
+ DomainVerificationLegacySettings().apply {
+ readSettings(it)
+ }
+ }
+
+ val xml = file.readText()
+
+ // Legacy migrated settings doesn't bother writing the legacy verification info
+ assertWithMessage(xml).that(newSettings.remove("com.test.one")).isNull()
+ assertWithMessage(xml).that(newSettings.getUserState("com.test.one", 0))
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+ assertWithMessage(xml).that(newSettings.getUserState("com.test.one", 10))
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)
+
+ val firstUserStates = newSettings.getUserStates("com.test.one")
+ assertWithMessage(xml).that(firstUserStates).isNotNull()
+ assertWithMessage(xml).that(firstUserStates!!.size()).isEqualTo(2)
+ assertWithMessage(xml).that(firstUserStates[0])
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+ assertWithMessage(xml).that(firstUserStates[10])
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)
+
+ assertWithMessage(xml).that(newSettings.remove("com.test.two")).isNull()
+ assertWithMessage(xml).that(newSettings.getUserStates("com.test.two")).isNull()
+
+ assertWithMessage(xml).that(newSettings.remove("com.test.three")).isNull()
+ assertWithMessage(xml).that(newSettings.getUserState("com.test.three", 11))
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
new file mode 100644
index 0000000..a76d8ce
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.verify.domain
+
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationUserSelection
+import com.android.server.pm.verify.domain.DomainVerificationPersistence
+
+operator fun <F> android.util.Pair<F, *>.component1() = first
+operator fun <S> android.util.Pair<*, S>.component2() = second
+
+operator fun DomainVerificationRequest.component1() = packageNames
+
+operator fun DomainVerificationInfo.component1() = identifier
+operator fun DomainVerificationInfo.component2() = packageName
+operator fun DomainVerificationInfo.component3() = hostToStateMap
+
+operator fun DomainVerificationUserSelection.component1() = identifier
+operator fun DomainVerificationUserSelection.component2() = packageName
+operator fun DomainVerificationUserSelection.component3() = user
+operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed
+operator fun DomainVerificationUserSelection.component5() = hostToUserSelectionMap
+
+operator fun DomainVerificationPersistence.ReadResult.component1() = active
+operator fun DomainVerificationPersistence.ReadResult.component2() = restored
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
new file mode 100644
index 0000000..a76152c
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.verify.domain
+
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.util.ArrayMap
+import android.util.TypedXmlPullParser
+import android.util.TypedXmlSerializer
+import android.util.Xml
+import com.android.server.pm.verify.domain.DomainVerificationPersistence
+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.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import java.io.File
+import java.nio.charset.StandardCharsets
+import java.util.UUID
+
+class DomainVerificationPersistenceTest {
+
+ companion object {
+ private val PKG_PREFIX = DomainVerificationPersistenceTest::class.java.`package`!!.name
+
+ internal fun File.writeXml(block: (serializer: TypedXmlSerializer) -> Unit) = apply {
+ outputStream().use {
+ // Explicitly use string based XML so it can printed in the test failure output
+ Xml.newFastSerializer()
+ .apply {
+ setOutput(it, StandardCharsets.UTF_8.name())
+ startDocument(null, true)
+ setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+ }
+ .apply(block)
+ .endDocument()
+ }
+ }
+
+ internal fun <T> File.readXml(block: (parser: TypedXmlPullParser) -> T) =
+ inputStream().use {
+ block(Xml.resolvePullParser(it))
+ }
+ }
+
+ @Rule
+ @JvmField
+ val tempFolder = TemporaryFolder()
+
+ @Test
+ fun writeAndReadBackNormal() {
+ val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply {
+ mockPkgState(0).let { put(it.packageName, it.id, it) }
+ mockPkgState(1).let { put(it.packageName, it.id, it) }
+ }
+ val pending = ArrayMap<String, DomainVerificationPkgState>().apply {
+ mockPkgState(2).let { put(it.packageName, it) }
+ mockPkgState(3).let { put(it.packageName, it) }
+ }
+ val restored = ArrayMap<String, DomainVerificationPkgState>().apply {
+ mockPkgState(4).let { put(it.packageName, it) }
+ mockPkgState(5).let { put(it.packageName, it) }
+ }
+
+ val file = tempFolder.newFile().writeXml {
+ DomainVerificationPersistence.writeToXml(it, attached, pending, restored)
+ }
+
+ val xml = file.readText()
+
+ val (readActive, readRestored) = file.readXml {
+ DomainVerificationPersistence.readFromXml(it)
+ }
+
+ assertWithMessage(xml).that(readActive.values)
+ .containsExactlyElementsIn(attached.values() + pending.values)
+ assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values)
+ }
+
+ @Test
+ fun readMalformed() {
+ val stateZero = mockEmptyPkgState(0).apply {
+ stateMap["example.com"] = DomainVerificationManager.STATE_SUCCESS
+ stateMap["example.org"] = DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED
+
+ // A domain without a written state falls back to default
+ stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE
+
+ userSelectionStates[1] = DomainVerificationUserState(1).apply {
+ addHosts(setOf("example-user1.com", "example-user1.org"))
+ isDisallowLinkHandling = false
+ }
+ }
+ val stateOne = mockEmptyPkgState(1).apply {
+ // It's valid to have a user selection without any autoVerify domains
+ userSelectionStates[1] = DomainVerificationUserState(1).apply {
+ addHosts(setOf("example-user1.com", "example-user1.org"))
+ isDisallowLinkHandling = true
+ }
+ }
+
+ // Also valid to have neither autoVerify domains nor any active user states
+ val stateTwo = mockEmptyPkgState(2, hasAutoVerifyDomains = false)
+
+ // language=XML
+ val xml = """
+ <?xml?>
+ <domain-verifications>
+ <active>
+ <package-state
+ packageName="${stateZero.packageName}"
+ id="${stateZero.id}"
+ >
+ <state>
+ <domain name="duplicate-takes-last.com" state="1"/>
+ </state>
+ </package-state>
+ <package-state
+ packageName="${stateZero.packageName}"
+ id="${stateZero.id}"
+ hasAutoVerifyDomains="true"
+ >
+ <state>
+ <domain name="example.com" state="${DomainVerificationManager.STATE_SUCCESS}"/>
+ <domain name="example.org" state="${DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
+ <not-domain name="not-domain.com" state="1"/>
+ <domain name="missing-state.com"/>
+ </state>
+ <user-states>
+ <user-state userId="1" disallowLinkHandling="false">
+ <enabled-hosts>
+ <host name="example-user1.com"/>
+ <not-host name="not-host.com"/>
+ <host/>
+ </enabled-hosts>
+ <enabled-hosts>
+ <host name="example-user1.org"/>
+ </enabled-hosts>
+ <enabled-hosts/>
+ </user-state>
+ <user-state>
+ <enabled-hosts>
+ <host name="no-user-id.com"/>
+ </enabled-hosts>
+ </user-state>
+ </user-states>
+ </package-state>
+ </active>
+ <not-active/>
+ <restored>
+ <package-state
+ packageName="${stateOne.packageName}"
+ id="${stateOne.id}"
+ hasAutoVerifyDomains="true"
+ >
+ <state/>
+ <user-states>
+ <user-state userId="1" disallowLinkHandling="true">
+ <enabled-hosts>
+ <host name="example-user1.com"/>
+ <host name="example-user1.org"/>
+ </enabled-hosts>
+ </user-state>
+ </user-states>
+ </package-state>
+ <package-state packageName="${stateTwo.packageName}"/>
+ <package-state id="${stateTwo.id}"/>
+ <package-state
+ packageName="${stateTwo.packageName}"
+ id="${stateTwo.id}"
+ hasAutoVerifyDomains="false"
+ >
+ <state/>
+ <user-states/>
+ </package-state>
+ </restore>
+ <not-restored/>
+ </domain-verifications>
+ """.trimIndent()
+
+ val (active, restored) = DomainVerificationPersistence
+ .readFromXml(Xml.resolvePullParser(xml.byteInputStream()))
+
+ assertThat(active.values).containsExactly(stateZero)
+ assertThat(restored.values).containsExactly(stateOne, stateTwo)
+ }
+
+ private fun mockEmptyPkgState(
+ id: Int,
+ hasAutoVerifyDomains: Boolean = true
+ ): DomainVerificationPkgState {
+ val pkgName = pkgName(id)
+ val domainSetId = UUID(0L, id.toLong())
+ return DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains)
+ }
+
+ private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply {
+ stateMap["$packageName.com"] = id
+ userSelectionStates[id] = DomainVerificationUserState(id).apply {
+ addHosts(setOf("$packageName-user.com"))
+ isDisallowLinkHandling = true
+ }
+ }
+
+ private fun pkgName(id: Int) = "${PKG_PREFIX}.pkg$id"
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
new file mode 100644
index 0000000..db541f6
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
@@ -0,0 +1,500 @@
+/*
+ * 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.test.verify.domain
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationState
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.server.DeviceIdleInternal
+import com.android.server.pm.verify.domain.DomainVerificationCollector
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2
+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
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.UUID
+
+@Suppress("DEPRECATION")
+class DomainVerificationProxyTest {
+
+ companion object {
+ private const val TEST_PKG_NAME_ONE = "com.test.pkg.one"
+ private const val TEST_PKG_NAME_TWO = "com.test.pkg.two"
+ private const val TEST_PKG_NAME_TARGET_ONE = "com.test.target.one"
+ private const val TEST_PKG_NAME_TARGET_TWO = "com.test.target.two"
+ private const val TEST_CALLING_UID_ACCEPT = 40
+ private const val TEST_CALLING_UID_REJECT = 41
+ private val TEST_UUID_ONE = UUID.fromString("f7fbb7dd-7b5f-4609-a95e-c6c7765fb9cd")
+ private val TEST_UUID_TWO = UUID.fromString("4a09b361-a967-43ac-9d18-07a385dff740")
+ }
+
+ private val componentOne = ComponentName(TEST_PKG_NAME_ONE, ".ReceiverOne")
+ private val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverTwo")
+ private val componentThree = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverThree")
+
+ private lateinit var context: Context
+ private lateinit var manager: DomainVerificationManagerInternal
+ private lateinit var collector: DomainVerificationCollector
+
+ // Must be declared as field to support generics
+ @Captor
+ lateinit var hostCaptor: ArgumentCaptor<Set<String>>
+
+ @Before
+ fun setUpMocks() {
+ MockitoAnnotations.initMocks(this)
+ context = mockThrowOnUnmocked {
+ whenever(sendBroadcastAsUser(any(), any(), any(), any<Bundle>()))
+ whenever(
+ enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT),
+ anyString()
+ )
+ )
+ }
+ manager = mockThrowOnUnmocked {
+ whenever(getDomainVerificationInfoId(any())) {
+ when (val pkgName = arguments[0] as String) {
+ TEST_PKG_NAME_TARGET_ONE -> TEST_UUID_ONE
+ TEST_PKG_NAME_TARGET_TWO -> TEST_UUID_TWO
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ whenever(getDomainVerificationInfo(anyString())) {
+ when (val pkgName = arguments[0] as String) {
+ TEST_PKG_NAME_TARGET_ONE -> DomainVerificationInfo(
+ TEST_UUID_ONE, pkgName, mapOf(
+ "example1.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+ "example2.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ )
+ )
+ TEST_PKG_NAME_TARGET_TWO -> DomainVerificationInfo(
+ TEST_UUID_TWO, pkgName, mapOf(
+ "example3.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+ "example4.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ )
+ )
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt()))
+ }
+ collector = mockThrowOnUnmocked {
+ whenever(collectAutoVerifyDomains(any())) {
+ when (val pkgName = (arguments[0] as AndroidPackage).packageName) {
+ TEST_PKG_NAME_TARGET_ONE -> ArraySet(setOf("example1.com", "example2.com"))
+ TEST_PKG_NAME_TARGET_TWO -> ArraySet(setOf("example3.com", "example4.com"))
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun isCallerVerifierV1() {
+ val connection = mockConnection()
+ val proxyV1 = DomainVerificationProxy.makeProxy<Connection>(
+ componentOne, null, context,
+ manager, collector, connection
+ )
+
+ assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)
+ verifyNoMoreInteractions(connection)
+ }
+
+ @Test
+ fun isCallerVerifierV2() {
+ val connection = mockConnection()
+ val proxyV2 = DomainVerificationProxy.makeProxy<Connection>(
+ null, componentTwo, context,
+ manager, collector, connection
+ )
+
+ assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ }
+
+ @Test
+ fun isCallerVerifierBoth() {
+ val connection = mockConnection()
+ val proxyBoth = DomainVerificationProxy.makeProxy<Connection>(
+ componentTwo, componentThree,
+ context, manager, collector, connection
+ )
+
+ // The combined proxy should only ever call v2 when it succeeds
+ assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ val callingUidCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+ // But will call both when v2 fails
+ assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection, times(2))
+ .isCallerPackage(callingUidCaptor.capture(), eq(TEST_PKG_NAME_TWO))
+ verifyNoMoreInteractions(connection)
+
+ assertThat(callingUidCaptor.allValues.toSet()).containsExactly(TEST_CALLING_UID_REJECT)
+ }
+
+ @Test
+ fun differentPackagesResolvesOnlyV2() {
+ assertThat(DomainVerificationProxy.makeProxy<Connection>(
+ componentOne, componentTwo,
+ context, manager, collector, mockConnection()
+ )).isInstanceOf(DomainVerificationProxyV2::class.java)
+ }
+
+ private fun prepareProxyV1(): ProxyV1Setup {
+ val messages = mutableListOf<Pair<Int, Any?>>()
+ val connection = mockConnection {
+ whenever(schedule(anyInt(), any())) {
+ messages.add((arguments[0] as Int) to arguments[1])
+ }
+ }
+
+ val proxy = DomainVerificationProxy.makeProxy<Connection>(
+ componentOne,
+ null,
+ context,
+ manager,
+ collector,
+ connection
+ )
+ return ProxyV1Setup(messages, connection, proxy)
+ }
+
+ @Test
+ fun sendBroadcastForPackagesV1() {
+ val (messages, _, proxy) = prepareProxyV1()
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context, times(2)).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+ verifyNoMoreInteractions(context)
+
+ val intents = intentCaptor.allValues
+ assertThat(intents).hasSize(2)
+ intents.forEach {
+ assertThat(it.action).isEqualTo(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
+ assertThat(it.getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME))
+ .isEqualTo(IntentFilter.SCHEME_HTTPS)
+ assertThat(it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1))
+ .isNotEqualTo(-1)
+ }
+
+ intents[0].apply {
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+ .isEqualTo(TEST_PKG_NAME_TARGET_ONE)
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+ .isEqualTo("example1.com example2.com")
+ }
+
+ intents[1].apply {
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+ .isEqualTo(TEST_PKG_NAME_TARGET_TWO)
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+ .isEqualTo("example3.com example4.com")
+ }
+ }
+
+ private fun prepareProxyOnIntentFilterVerifiedV1(): Pair<ProxyV1Setup, Pair<Int, Int>> {
+ val (messages, connection, proxy) = prepareProxyV1()
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+ messages.clear()
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context, times(2)).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+
+ val verificationIds = intentCaptor.allValues.map {
+ it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1)
+ }
+
+ assertThat(verificationIds).doesNotContain(-1)
+
+ return ProxyV1Setup(messages, connection, proxy) to
+ (verificationIds[0] to verificationIds[1])
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedFullSuccessV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+ emptyList(),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+ emptyList(),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ assertThat(messages).hasSize(2)
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(2)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ eq(DomainVerificationManager.STATE_SUCCESS)
+ )
+
+ assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+ assertThat(hostCaptor.allValues.toSet()).containsExactly(
+ setOf("example1.com", "example2.com"),
+ setOf("example3.com", "example4.com")
+ )
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedPartialSuccessV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example1.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example3.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+ val stateCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(4)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ stateCaptor.capture()
+ )
+
+ assertThat(idCaptor.allValues)
+ .containsExactly(TEST_UUID_ONE, TEST_UUID_ONE, TEST_UUID_TWO, TEST_UUID_TWO)
+
+ val hostToStates: Map<Set<*>, Int> = hostCaptor.allValues.zip(stateCaptor.allValues).toMap()
+ assertThat(hostToStates).isEqualTo(mapOf(
+ setOf("example1.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+ setOf("example2.com") to DomainVerificationState.STATE_SUCCESS,
+ setOf("example3.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+ setOf("example4.com") to DomainVerificationState.STATE_SUCCESS,
+ ))
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedFailureV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example1.com", "example2.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example3.com", "example4.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(2)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ eq(DomainVerificationState.STATE_LEGACY_FAILURE)
+ )
+
+ assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+ assertThat(hostCaptor.allValues.toSet()).containsExactly(
+ setOf("example1.com", "example2.com"),
+ setOf("example3.com", "example4.com")
+ )
+ }
+
+ @Test
+ fun sendBroadcastForPackagesV2() {
+ val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverOne")
+ val messages = mutableListOf<Pair<Int, Any?>>()
+
+ val connection = mockConnection {
+ whenever(schedule(anyInt(), any())) {
+ messages.add((arguments[0] as Int) to arguments[1])
+ }
+ }
+
+ val proxy = DomainVerificationProxy.makeProxy<Connection>(
+ null,
+ componentTwo,
+ context,
+ manager,
+ collector,
+ connection
+ )
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+ verifyNoMoreInteractions(context)
+
+ val intents = intentCaptor.allValues
+ assertThat(intents).hasSize(1)
+ intents.single().apply {
+ assertThat(this.action).isEqualTo(Intent.ACTION_DOMAINS_NEED_VERIFICATION)
+ val request: DomainVerificationRequest? =
+ getParcelableExtra(DomainVerificationManager.EXTRA_VERIFICATION_REQUEST)
+ assertThat(request?.packageNames).containsExactly(
+ TEST_PKG_NAME_TARGET_ONE,
+ TEST_PKG_NAME_TARGET_TWO
+ )
+ }
+ }
+
+ private fun mockConnection(block: Connection.() -> Unit = {}) =
+ mockThrowOnUnmocked<Connection> {
+ whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)) { true }
+ whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)) { true }
+ whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)) { false }
+ whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)) { false }
+ whenever(getPackage(anyString())) { mockPkg(arguments[0] as String) }
+ whenever(powerSaveTempWhitelistAppDuration) { 1000 }
+ whenever(deviceIdleInternal) {
+ mockThrowOnUnmocked<DeviceIdleInternal> {
+ whenever(
+ addPowerSaveTempWhitelistApp(
+ anyInt(), anyString(), anyLong(), anyInt(),
+ anyBoolean(), anyString()
+ )
+ )
+ }
+ }
+ block()
+ }
+
+ private fun mockPkg(pkgName: String): AndroidPackage {
+ return mockThrowOnUnmocked { whenever(packageName) { pkgName } }
+ }
+
+ private data class ProxyV1Setup(
+ val messages: MutableList<Pair<Int, Any?>>,
+ val connection: Connection,
+ val proxy: DomainVerificationProxy
+ )
+
+ interface Connection : DomainVerificationProxyV1.Connection,
+ DomainVerificationProxyV2.Connection
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
index a18632b..961fc18 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
@@ -232,8 +232,8 @@
ai.packageName = packageName;
ai.uid = uid;
ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
- app.pid = pid;
- mAms.mPidsSelfLocked.doAddInternal(app);
+ app.setPid(pid);
+ mAms.mPidsSelfLocked.doAddInternal(app.getPid(), app);
mPhantomInjector.addToProcess(uid, pid, pid);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 9441ecf..22b2f7e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -798,7 +798,7 @@
final int traceEnd = 8192;
createRandomFile(traceFile, traceSize);
assertEquals(traceSize, traceFile.length());
- mAppExitInfoTracker.handleLogAnrTrace(app.pid, app.uid, app.getPackageList(),
+ mAppExitInfoTracker.handleLogAnrTrace(app.getPid(), app.uid, app.getPackageList(),
traceFile, traceStart, traceEnd);
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
@@ -991,16 +991,16 @@
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = packageName;
ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
- app.pid = pid;
+ app.setPid(pid);
app.info.uid = packageUid;
if (definingUid != null) {
final String dummyPackageName = "com.android.test";
final String dummyClassName = ".Foo";
- app.hostingRecord = HostingRecord.byAppZygote(new ComponentName(
- dummyPackageName, dummyClassName), "", definingUid);
+ app.setHostingRecord(HostingRecord.byAppZygote(new ComponentName(
+ dummyPackageName, dummyClassName), "", definingUid));
}
- app.connectionGroup = connectionGroup;
- app.setProcState = procState;
+ app.mServices.setConnectionGroup(connectionGroup);
+ app.mState.setSetProcState(procState);
app.mProfile.setLastMemInfo(spy(new Debug.MemoryInfo()));
app.mProfile.setLastPss(pss);
app.mProfile.setLastRss(rss);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index c82db73..022fadc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -170,7 +170,7 @@
/* lruWeight= */1.0f);
ProcessList list = new ProcessList();
- ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
processList.add(lastUsed40MinutesAgo);
@@ -191,7 +191,7 @@
Duration.ofMinutes(30).toMillis(), 1024L, 20);
processList.add(lastUsed30MinutesAgo);
- mCacheOomRanker.reRankLruCachedApps(list);
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 5 ordered by least recently used first, then last processes position unchanged.
assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo,
@@ -207,7 +207,7 @@
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
processList.add(rss10k);
@@ -231,7 +231,7 @@
Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20);
processList.add(rss16k);
- mCacheOomRanker.reRankLruCachedApps(list);
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 6 ordered by largest pss, then last processes position unchanged.
assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
@@ -246,8 +246,8 @@
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.mLruProcessServiceStart = 1;
- ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ list.setLruProcessServiceStartLSP(1);
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
processList.add(used1000);
@@ -268,7 +268,7 @@
Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
processList.add(used200);
- mCacheOomRanker.reRankLruCachedApps(list);
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 4 ordered by uses, then last processes position unchanged.
assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500,
@@ -283,7 +283,7 @@
/* lruWeight= */ 0.3f);
ProcessList list = new ProcessList();
- ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
processList.add(unknownAdj1);
@@ -304,7 +304,7 @@
processList.add(systemAdj);
// 6 Processes but only 3 in eligible for cache so no re-ranking.
- mCacheOomRanker.reRankLruCachedApps(list);
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// All positions unchanged.
assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3,
@@ -319,8 +319,8 @@
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.mLruProcessServiceStart = 4;
- ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ list.setLruProcessServiceStartLSP(4);
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
processList.add(used1000);
@@ -340,7 +340,7 @@
Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
processList.add(used200);
- mCacheOomRanker.reRankLruCachedApps(list);
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// All positions unchanged.
assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
@@ -378,17 +378,17 @@
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = "a.package.name" + mNextPackageName++;
ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++);
- app.pid = mNextPid++;
+ app.setPid(mNextPid++);
app.info.uid = mNextPackageUid++;
// Exact value does not mater, it can be any state for which compaction is allowed.
- app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- app.setAdj = setAdj;
- app.lastActivityTime = lastActivityTime;
+ app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ app.mState.setSetAdj(setAdj);
+ app.setLastActivityTime(lastActivityTime);
app.mProfile.setLastRss(lastRss);
- app.setCached(false);
+ app.mState.setCached(false);
for (int i = 0; i < returnedToCacheCount; ++i) {
- app.setCached(false);
- app.setCached(true);
+ app.mState.setCached(false);
+ app.mState.setCached(true);
}
return app;
}
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 96a44a4..d860326 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -134,11 +134,11 @@
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = packageName;
ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
- app.pid = pid;
+ app.setPid(pid);
app.info.uid = packageUid;
// Exact value does not mater, it can be any state for which compaction is allowed.
- app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- app.setAdj = 905;
+ app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ app.mState.setSetAdj(905);
return app;
}
@@ -875,7 +875,8 @@
mProcessDependencies.setRss(rssBefore2);
mProcessDependencies.setRssAfterCompaction(rssAfter2);
// This is to avoid throttle of compacting too soon.
- processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+ processRecord.mOptRecord.setLastCompactTime(
+ processRecord.mOptRecord.getLastCompactTime() - 10_000);
// WHEN we try to run compaction.
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
waitForHandler();
@@ -890,7 +891,8 @@
mProcessDependencies.setRss(rssBefore3);
mProcessDependencies.setRssAfterCompaction(rssAfter3);
// This is to avoid throttle of compacting too soon.
- processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+ processRecord.mOptRecord.setLastCompactTime(
+ processRecord.mOptRecord.getLastCompactTime() - 10_000);
// WHEN we try to run compaction
mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
waitForHandler();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 7daf357..27825a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -176,6 +176,8 @@
setFieldValue(ActivityManagerService.class, sService, "mUserController",
mock(UserController.class));
setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
+ setFieldValue(ActivityManagerService.class, sService, "mProcLock",
+ new ActivityManagerProcLock());
setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
.enqueueProcessChangeItemLocked(anyInt(), anyInt());
@@ -207,8 +209,8 @@
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Sleeping() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.maxAdj = PERSISTENT_PROC_ADJ;
- app.setHasTopUi(true);
+ app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ app.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -222,8 +224,8 @@
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Awake() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.maxAdj = PERSISTENT_PROC_ADJ;
- app.setHasTopUi(true);
+ app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ app.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -236,7 +238,7 @@
public void testUpdateOomAdj_DoOne_Persistent_TopApp() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.maxAdj = PERSISTENT_PROC_ADJ;
+ app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -266,7 +268,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
- app.runningRemoteAnimation = true;
+ app.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
@@ -308,7 +310,7 @@
public void testUpdateOomAdj_DoOne_ExecutingService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.executingServices.add(mock(ServiceRecord.class));
+ app.mServices.startExecutingService(mock(ServiceRecord.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -337,7 +339,7 @@
public void testUpdateOomAdj_DoOne_CachedEmpty() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.setCurRawAdj(CACHED_APP_MIN_ADJ);
+ app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -351,7 +353,6 @@
public void testUpdateOomAdj_DoOne_VisibleActivities() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).hasActivities();
doAnswer(answer(callback -> {
@@ -368,7 +369,6 @@
any(WindowProcessController.ComputeOomAdjCallback.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- doCallRealMethod().when(app).getWindowProcessController();
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
}
@@ -378,15 +378,14 @@
public void testUpdateOomAdj_DoOne_RecentTasks() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).hasRecentTasks();
- app.lastTopTime = SystemClock.uptimeMillis();
+ app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
doCallRealMethod().when(wpc).hasRecentTasks();
- assertEquals(PROCESS_STATE_CACHED_RECENT, app.setProcState);
+ assertEquals(PROCESS_STATE_CACHED_RECENT, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -394,7 +393,7 @@
public void testUpdateOomAdj_DoOne_FgServiceLocation() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+ app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -407,7 +406,7 @@
public void testUpdateOomAdj_DoOne_FgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.setHasForegroundServices(true, 0);
+ app.mServices.setHasForegroundServices(true, 0);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -420,7 +419,7 @@
public void testUpdateOomAdj_DoOne_OverlayUi() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.setHasOverlayUi(true);
+ app.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -433,8 +432,8 @@
public void testUpdateOomAdj_DoOne_PerceptibleRecent() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.setHasForegroundServices(true, 0);
- app.lastTopTime = SystemClock.uptimeMillis();
+ app.mServices.setHasForegroundServices(true, 0);
+ app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -447,7 +446,7 @@
public void testUpdateOomAdj_DoOne_Toast() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.forcingToImportant = new Object();
+ app.mState.setForcingToImportant(new Object());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -460,7 +459,6 @@
public void testUpdateOomAdj_DoOne_HeavyWeight() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHeavyWeightProcess();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -476,7 +474,6 @@
public void testUpdateOomAdj_DoOne_HomeApp() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -490,7 +487,6 @@
public void testUpdateOomAdj_DoOne_PreviousApp() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isPreviousProcess();
doReturn(true).when(wpc).hasActivities();
@@ -522,11 +518,11 @@
public void testUpdateOomAdj_DoOne_ClientActivities() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(true).when(app).hasClientActivities();
+ app.mServices.setHasClientActivities(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.setProcState);
+ assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -534,11 +530,11 @@
public void testUpdateOomAdj_DoOne_TreatLikeActivity() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.treatLikeActivity = true;
+ app.mServices.setTreatLikeActivity(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.setProcState);
+ assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -546,12 +542,12 @@
public void testUpdateOomAdj_DoOne_ServiceB() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.serviceb = true;
+ app.mState.setServiceB(true);
ServiceRecord s = mock(ServiceRecord.class);
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
s.startRequested = true;
s.lastActivity = SystemClock.uptimeMillis();
- app.startService(s);
+ app.mServices.startService(s);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -563,7 +559,7 @@
public void testUpdateOomAdj_DoOne_MaxAdj() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.maxAdj = PERCEPTIBLE_LOW_APP_ADJ;
+ app.mState.setMaxAdj(PERCEPTIBLE_LOW_APP_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -576,14 +572,14 @@
public void testUpdateOomAdj_DoOne_NonCachedToCached() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.setCached(false);
- app.setCurRawAdj(SERVICE_ADJ);
+ app.mState.setCached(false);
+ app.mState.setCurRawAdj(SERVICE_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.setAdj);
- assertTrue(ProcessList.CACHED_APP_MAX_ADJ >= app.setAdj);
+ assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.mState.getSetAdj());
+ assertTrue(ProcessList.CACHED_APP_MAX_ADJ >= app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -595,7 +591,7 @@
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
s.startRequested = true;
s.lastActivity = SystemClock.uptimeMillis();
- app.startService(s);
+ app.mServices.startService(s);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -633,7 +629,7 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.setProcState);
+ assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -653,8 +649,8 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(FOREGROUND_APP_ADJ, app.setAdj);
- assertEquals(SCHED_GROUP_TOP_APP_BOUND, app.setSchedGroup);
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+ assertEquals(SCHED_GROUP_TOP_APP_BOUND, app.mState.getSetSchedGroup());
}
@SuppressWarnings("GuardedBy")
@@ -676,12 +672,12 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.treatLikeActivity = true;
+ client.mServices.setTreatLikeActivity(true);
bindService(app, client, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_CACHED_EMPTY, app.setProcState);
+ assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -689,7 +685,6 @@
public void testUpdateOomAdj_DoOne_Service_AllowOomManagement() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
WindowProcessController wpc = app.getWindowProcessController();
doReturn(false).when(wpc).isHomeProcess();
doReturn(true).when(wpc).isPreviousProcess();
@@ -703,7 +698,7 @@
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
doReturn(null).when(sService).getTopApp();
- assertEquals(PREVIOUS_APP_ADJ, app.setAdj);
+ assertEquals(PREVIOUS_APP_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -714,8 +709,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
- client.maxAdj = PERSISTENT_PROC_ADJ;
- client.setHasTopUi(true);
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ client.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -731,11 +726,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, Context.BIND_IMPORTANT, mock(IBinder.class));
- client.executingServices.add(mock(ServiceRecord.class));
+ client.mServices.startExecutingService(mock(ServiceRecord.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(FOREGROUND_APP_ADJ, app.setAdj);
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -763,11 +758,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
- client.maxAdj = PERSISTENT_PROC_ADJ;
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.setProcState);
+ assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -778,11 +773,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
- client.maxAdj = PERSISTENT_PROC_ADJ;
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.setProcState);
+ assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -793,11 +788,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, 0, mock(IBinder.class));
- client.setHasForegroundServices(true, 0);
+ client.mServices.setHasForegroundServices(true, 0);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.setProcState);
+ assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -815,12 +810,12 @@
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
doReturn(null).when(sService.mBackupTargets).get(anyInt());
- assertEquals(BACKUP_APP_ADJ, app.setAdj);
+ assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
- client.maxAdj = PERSISTENT_PROC_ADJ;
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PERSISTENT_SERVICE_ADJ, app.setAdj);
+ assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -831,11 +826,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
- client.runningRemoteAnimation = true;
+ client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.setAdj);
+ assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -846,11 +841,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class));
- client.runningRemoteAnimation = true;
+ client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PERCEPTIBLE_APP_ADJ, app.setAdj);
+ assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -861,11 +856,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, 0, mock(IBinder.class));
- client.setHasOverlayUi(true);
+ client.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PERCEPTIBLE_APP_ADJ, app.setAdj);
+ assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -876,11 +871,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, 0, mock(IBinder.class));
- client.runningRemoteAnimation = true;
+ client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(VISIBLE_APP_ADJ, app.setAdj);
+ assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -891,11 +886,11 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, Context.BIND_IMPORTANT_BACKGROUND, mock(IBinder.class));
- client.setHasOverlayUi(true);
+ client.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.setProcState);
+ assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -917,7 +912,7 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindProvider(app, client, null, null, false);
- client.treatLikeActivity = true;
+ client.mServices.setTreatLikeActivity(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -948,7 +943,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.setHasForegroundServices(true, 0);
+ client.mServices.setHasForegroundServices(true, 0);
bindProvider(app, client, null, null, false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -977,7 +972,7 @@
public void testUpdateOomAdj_DoOne_Provider_Retention() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.lastProviderTime = SystemClock.uptimeMillis();
+ app.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1017,7 +1012,7 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app, client2, null, 0, mock(IBinder.class));
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1036,7 +1031,7 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, 0, mock(IBinder.class));
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1055,9 +1050,9 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, 0, mock(IBinder.class));
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
bindService(client2, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app);
lru.add(client);
@@ -1072,13 +1067,13 @@
assertProcStates(client2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
- client2.setHasForegroundServices(false, 0);
+ client2.mServices.setHasForegroundServices(false, 0);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(client2, true, OomAdjuster.OOM_ADJ_REASON_NONE);
- assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.setProcState);
- assertEquals(PROCESS_STATE_CACHED_EMPTY, client.setProcState);
- assertEquals(PROCESS_STATE_CACHED_EMPTY, app.setProcState);
+ assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
+ assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
+ assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -1093,8 +1088,8 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client2, client, null, 0, mock(IBinder.class));
- client.setHasForegroundServices(true, 0);
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ client.mServices.setHasForegroundServices(true, 0);
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app);
lru.add(client);
@@ -1121,11 +1116,11 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, 0, mock(IBinder.class));
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
bindService(client2, app, null, 0, mock(IBinder.class));
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.forcingToImportant = new Object();
+ client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1146,12 +1141,11 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, 0, mock(IBinder.class));
bindService(client2, app, null, 0, mock(IBinder.class));
- doReturn(mock(WindowProcessController.class)).when(client2).getWindowProcessController();
WindowProcessController wpc = client2.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.forcingToImportant = new Object();
+ client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1172,14 +1166,13 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, 0, mock(IBinder.class));
bindService(client2, app, null, 0, mock(IBinder.class));
- doReturn(mock(WindowProcessController.class)).when(client2).getWindowProcessController();
WindowProcessController wpc = client2.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- client4.forcingToImportant = new Object();
+ client4.mState.setForcingToImportant(new Object());
bindService(app, client4, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1200,16 +1193,15 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, 0, mock(IBinder.class));
bindService(client2, app, null, 0, mock(IBinder.class));
- doReturn(mock(WindowProcessController.class)).when(client2).getWindowProcessController();
WindowProcessController wpc = client2.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.forcingToImportant = new Object();
+ client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- client4.setHasForegroundServices(true, 0);
+ client4.mServices.setHasForegroundServices(true, 0);
bindService(app, client4, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1225,17 +1217,16 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- doReturn(mock(WindowProcessController.class)).when(client).getWindowProcessController();
WindowProcessController wpc = client.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
bindService(app, client, null, 0, mock(IBinder.class));
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app, client2, null, 0, mock(IBinder.class));
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.forcingToImportant = new Object();
+ client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1255,7 +1246,7 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1274,7 +1265,7 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
bindService(client2, app, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1294,7 +1285,7 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1313,7 +1304,7 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.setHasForegroundServices(true, 0);
+ client2.mServices.setHasForegroundServices(true, 0);
bindProvider(client2, app, null, null, false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1327,11 +1318,11 @@
public void testUpdateOomAdj_DoAll_Unbound() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.forcingToImportant = new Object();
+ app.mState.setForcingToImportant(new Object());
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- app2.setHasForegroundServices(true, 0);
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ app2.mServices.setHasForegroundServices(true, 0);
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app);
lru.add(app2);
@@ -1350,12 +1341,12 @@
public void testUpdateOomAdj_DoAll_BoundFgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.forcingToImportant = new Object();
+ app.mState.setForcingToImportant(new Object());
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- app2.setHasForegroundServices(true, 0);
+ app2.mServices.setHasForegroundServices(true, 0);
bindService(app, app2, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app);
lru.add(app2);
@@ -1380,9 +1371,9 @@
ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app2, app3, null, 0, mock(IBinder.class));
- app3.setHasForegroundServices(true, 0);
+ app3.mServices.setHasForegroundServices(true, 0);
bindService(app3, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app);
lru.add(app2);
@@ -1397,15 +1388,15 @@
SCHED_GROUP_DEFAULT);
assertProcStates(app3, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
- assertEquals("service", app.adjType);
- assertEquals("service", app2.adjType);
- assertEquals("fg-service", app3.adjType);
+ assertEquals("service", app.mState.getAdjType());
+ assertEquals("service", app2.mState.getAdjType());
+ assertEquals("fg-service", app3.mState.getAdjType());
assertEquals(false, app.isCached());
assertEquals(false, app2.isCached());
assertEquals(false, app3.isCached());
- assertEquals(false, app.empty);
- assertEquals(false, app2.empty);
- assertEquals(false, app3.empty);
+ assertEquals(false, app.mState.isEmpty());
+ assertEquals(false, app2.mState.isEmpty());
+ assertEquals(false, app3.mState.isEmpty());
}
@SuppressWarnings("GuardedBy")
@@ -1420,18 +1411,17 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app2, app3, null, 0, mock(IBinder.class));
bindService(app3, app, null, 0, mock(IBinder.class));
- doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
WindowProcessController wpc = app3.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.setHasOverlayUi(true);
+ app4.mState.setHasOverlayUi(true);
bindService(app, app4, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.setHasForegroundServices(true, 0);
+ app5.mServices.setHasForegroundServices(true, 0);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app);
lru.add(app2);
@@ -1466,18 +1456,17 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app2, app3, null, 0, mock(IBinder.class));
bindService(app3, app, null, 0, mock(IBinder.class));
- doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
WindowProcessController wpc = app3.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.setHasOverlayUi(true);
+ app4.mState.setHasOverlayUi(true);
bindService(app, app4, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.setHasForegroundServices(true, 0);
+ app5.mServices.setHasForegroundServices(true, 0);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app5);
lru.add(app4);
@@ -1512,18 +1501,17 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app2, app3, null, 0, mock(IBinder.class));
bindService(app3, app, null, 0, mock(IBinder.class));
- doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
WindowProcessController wpc = app3.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.setHasOverlayUi(true);
+ app4.mState.setHasOverlayUi(true);
bindService(app, app4, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.setHasForegroundServices(true, 0);
+ app5.mServices.setHasForegroundServices(true, 0);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app3);
lru.add(app4);
@@ -1558,18 +1546,17 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(app2, app3, null, null, false);
bindProvider(app3, app, null, null, false);
- doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
WindowProcessController wpc = app3.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.setHasOverlayUi(true);
+ app4.mState.setHasOverlayUi(true);
bindProvider(app, app4, cr, null, false);
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.setHasForegroundServices(true, 0);
+ app5.mServices.setHasForegroundServices(true, 0);
bindProvider(app, app5, cr, null, false);
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app);
lru.add(app2);
@@ -1612,11 +1599,11 @@
s.app = app3;
setFieldValue(ServiceRecord.class, s, "connections",
new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
- app3.startService(s);
+ app3.mServices.startService(s);
doCallRealMethod().when(s).getConnections();
s.startRequested = true;
s.lastActivity = now;
- ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app3);
lru.add(app2);
@@ -1626,9 +1613,9 @@
sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
lru.clear();
- assertEquals(SERVICE_B_ADJ, app3.setAdj);
- assertEquals(SERVICE_ADJ, app2.setAdj);
- assertEquals(SERVICE_ADJ, app.setAdj);
+ assertEquals(SERVICE_B_ADJ, app3.mState.getSetAdj());
+ assertEquals(SERVICE_ADJ, app2.mState.getSetAdj());
+ assertEquals(SERVICE_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@@ -1644,7 +1631,7 @@
final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
doReturn(userOwner).when(sService.mUserController).getCurrentUserId();
- final ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+ final ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
lru.add(app2);
lru.add(app);
@@ -1664,9 +1651,9 @@
s.startRequested = true;
s.lastActivity = now;
- app.setCached(false);
- app.startService(s);
- app.hasShownUi = true;
+ app.mState.setCached(false);
+ app.mServices.startService(s);
+ app.mState.setHasShownUi(true);
final ServiceInfo si2 = mock(ServiceInfo.class);
si2.applicationInfo = mock(ApplicationInfo.class);
@@ -1677,9 +1664,9 @@
s2.startRequested = true;
s2.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- app2.setCached(false);
- app2.startService(s2);
- app2.hasShownUi = false;
+ app2.mState.setCached(false);
+ app2.mServices.startService(s2);
+ app2.mState.setHasShownUi(false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1687,28 +1674,28 @@
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
- app.setProcState = PROCESS_STATE_NONEXISTENT;
- app.adjType = null;
- app.setAdj = UNKNOWN_ADJ;
- app.hasShownUi = false;
+ app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+ app.mState.setAdjType(null);
+ app.mState.setSetAdj(UNKNOWN_ADJ);
+ app.mState.setHasShownUi(false);
sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
- app.setCached(false);
- app.setProcState = PROCESS_STATE_NONEXISTENT;
- app.adjType = null;
- app.setAdj = UNKNOWN_ADJ;
+ app.mState.setCached(false);
+ app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+ app.mState.setAdjType(null);
+ app.mState.setSetAdj(UNKNOWN_ADJ);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
- app.stopService(s);
- app.setProcState = PROCESS_STATE_NONEXISTENT;
- app.adjType = null;
- app.setAdj = UNKNOWN_ADJ;
- app.hasShownUi = true;
+ app.mServices.stopService(s);
+ app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+ app.mState.setAdjType(null);
+ app.mState.setSetAdj(UNKNOWN_ADJ);
+ app.mState.setHasShownUi(true);
sService.mConstants.KEEP_WARMING_SERVICES.add(cn);
sService.mConstants.KEEP_WARMING_SERVICES.add(cn2);
s = spy(new ServiceRecord(sService, cn, cn, null, 0, null,
@@ -1717,17 +1704,17 @@
s.startRequested = true;
s.lastActivity = now;
- app.startService(s);
+ app.mServices.startService(s);
sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
- app.setCached(true);
- app.setProcState = PROCESS_STATE_NONEXISTENT;
- app.adjType = null;
- app.setAdj = UNKNOWN_ADJ;
- app.hasShownUi = false;
+ app.mState.setCached(true);
+ app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+ app.mState.setAdjType(null);
+ app.mState.setSetAdj(UNKNOWN_ADJ);
+ app.mState.setHasShownUi(false);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1776,49 +1763,55 @@
ai.longVersionCode = versionCode;
ai.targetSdkVersion = targetSdkVersion;
ProcessRecord app = new ProcessRecord(service, ai, processName, uid);
+ final ProcessStateRecord state = app.mState;
+ final ProcessServiceRecord services = app.mServices;
+ final ProcessReceiverRecord receivers = app.mReceivers;
final ProcessProfileRecord profile = app.mProfile;
- app.thread = mock(IApplicationThread.class);
- app.lastActivityTime = lastActivityTime;
+ final ProcessProviderRecord providers = app.mProviders;
+ app.makeActive(mock(IApplicationThread.class), sService.mProcessStats);
+ app.setLastActivityTime(lastActivityTime);
+ app.setKilledByAm(killedByAm);
+ app.setIsolatedEntryPoint(isolatedEntryPoint);
+ setFieldValue(ProcessRecord.class, app, "mWindowProcessController",
+ mock(WindowProcessController.class));
profile.setLastPssTime(lastPssTime);
profile.setNextPssTime(nextPssTime);
profile.setLastPss(lastPss);
- app.maxAdj = maxAdj;
- app.setRawAdj = setRawAdj;
- app.curAdj = curAdj;
- app.setAdj = setAdj;
- app.setCurrentSchedulingGroup(curSchedGroup);
- app.setSchedGroup = setSchedGroup;
- app.setCurProcState(curProcState);
- app.setReportedProcState(repProcState);
- app.setCurRawProcState(curRawProcState);
- app.setProcState = setProcState;
- app.connectionGroup = connectionGroup;
- app.connectionImportance = connectionImportance;
- app.serviceb = serviceb;
- app.setHasClientActivities(hasClientActivities);
- app.setHasForegroundServices(hasForegroundServices, fgServiceTypes);
- app.setHasClientActivities(hasForegroundActivities);
- app.repForegroundActivities = repForegroundActivities;
- app.systemNoUi = systemNoUi;
- app.hasShownUi = hasShownUi;
- app.setHasTopUi(hasTopUi);
- app.setHasOverlayUi(hasOverlayUi);
- app.runningRemoteAnimation = runningRemoteAnimation;
- app.hasAboveClient = hasAboveClient;
- app.treatLikeActivity = treatLikeActivity;
- app.killedByAm = killedByAm;
- app.forcingToImportant = forcingToImportant;
- for (int i = 0; i < numOfCurReceivers; i++) {
- app.curReceivers.add(mock(BroadcastRecord.class));
- }
- app.lastProviderTime = lastProviderTime;
- app.lastTopTime = lastTopTime;
- app.setCached(cached);
+ state.setMaxAdj(maxAdj);
+ state.setSetRawAdj(setRawAdj);
+ state.setCurAdj(curAdj);
+ state.setSetAdj(setAdj);
+ state.setCurrentSchedulingGroup(curSchedGroup);
+ state.setSetSchedGroup(setSchedGroup);
+ state.setCurProcState(curProcState);
+ state.setReportedProcState(repProcState);
+ state.setCurRawProcState(curRawProcState);
+ state.setSetProcState(setProcState);
+ state.setServiceB(serviceb);
+ state.setRepForegroundActivities(repForegroundActivities);
+ state.setHasForegroundActivities(hasForegroundActivities);
+ state.setSystemNoUi(systemNoUi);
+ state.setHasShownUi(hasShownUi);
+ state.setHasTopUi(hasTopUi);
+ state.setRunningRemoteAnimation(runningRemoteAnimation);
+ state.setHasOverlayUi(hasOverlayUi);
+ state.setCached(cached);
+ state.setLastTopTime(lastTopTime);
+ state.setForcingToImportant(forcingToImportant);
+ services.setConnectionGroup(connectionGroup);
+ services.setConnectionImportance(connectionImportance);
+ services.setHasClientActivities(hasClientActivities);
+ services.setHasForegroundServices(hasForegroundServices, fgServiceTypes);
+ services.setHasAboveClient(hasAboveClient);
+ services.setTreatLikeActivity(treatLikeActivity);
+ services.setExecServicesFg(execServicesFg);
for (int i = 0; i < numOfExecutingServices; i++) {
- app.executingServices.add(mock(ServiceRecord.class));
+ services.startExecutingService(mock(ServiceRecord.class));
}
- app.isolatedEntryPoint = isolatedEntryPoint;
- app.execServicesFg = execServicesFg;
+ for (int i = 0; i < numOfCurReceivers; i++) {
+ receivers.addCurReceiver(mock(BroadcastRecord.class));
+ }
+ providers.setLastProviderTime(lastProviderTime);
return app;
}
@@ -1829,7 +1822,7 @@
record.app = service;
setFieldValue(ServiceRecord.class, record, "connections",
new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
- service.startService(record);
+ service.mServices.startService(record);
doCallRealMethod().when(record).getConnections();
}
AppBindRecord binding = new AppBindRecord(record, null, client);
@@ -1840,7 +1833,7 @@
doCallRealMethod().when(record).addConnection(any(IBinder.class),
any(ConnectionRecord.class));
record.addConnection(binder, cr);
- client.connections.add(cr);
+ client.mServices.addConnection(cr);
binding.connections.add(cr);
doNothing().when(cr).trackProcState(anyInt(), anyInt(), anyLong());
return record;
@@ -1850,7 +1843,7 @@
ContentProviderRecord record, String name, boolean hasExternalProviders) {
if (record == null) {
record = mock(ContentProviderRecord.class);
- publisher.pubProviders.put(name, record);
+ publisher.mProviders.installProvider(name, record);
record.proc = publisher;
setFieldValue(ContentProviderRecord.class, record, "connections",
new ArrayList<ContentProviderConnection>());
@@ -1859,22 +1852,24 @@
ContentProviderConnection conn = spy(new ContentProviderConnection(record, client,
client.info.packageName));
record.connections.add(conn);
- client.conProviders.add(conn);
+ client.mProviders.addProviderConnection(conn);
return record;
}
private void assertProcStates(ProcessRecord app, int expectedProcState, int expectedAdj,
int expectedSchedGroup) {
- assertEquals(expectedProcState, app.setProcState);
- assertEquals(expectedAdj, app.setAdj);
- assertEquals(expectedSchedGroup, app.setSchedGroup);
+ final ProcessStateRecord state = app.mState;
+ assertEquals(expectedProcState, state.getSetProcState());
+ assertEquals(expectedAdj, state.getSetAdj());
+ assertEquals(expectedSchedGroup, state.getSetSchedGroup());
}
private void assertProcStates(ProcessRecord app, boolean expectedCached,
int expectedProcState, int expectedAdj, String expectedAdjType) {
- assertEquals(expectedCached, app.isCached());
- assertEquals(expectedProcState, app.setProcState);
- assertEquals(expectedAdj, app.setAdj);
- assertEquals(expectedAdjType, app.adjType);
+ final ProcessStateRecord state = app.mState;
+ assertEquals(expectedCached, state.isCached());
+ assertEquals(expectedProcState, state.getSetProcState());
+ assertEquals(expectedAdj, state.getSetAdj());
+ assertEquals(expectedAdjType, state.getAdjType());
}
}
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 84bfc9b..3b5cc88 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
@@ -96,6 +96,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -383,7 +384,7 @@
LocationResult loc = createLocationResult(NAME, mRandom);
mProvider.setProviderLocation(loc);
- verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class));
+ verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
}
@Test
@@ -406,13 +407,13 @@
LocationResult loc = createLocationResult(NAME, mRandom);
mProvider.setProviderLocation(loc);
- verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class));
+ verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, false);
loc = createLocationResult(NAME, mRandom);
mProvider.setProviderLocation(loc);
- verify(listener, times(1)).onLocationChanged(any(LocationResult.class),
+ verify(listener, times(1)).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
@@ -422,7 +423,7 @@
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, false);
loc = createLocationResult(NAME, mRandom);
mProvider.setProviderLocation(loc);
- verify(listener, times(1)).onLocationChanged(any(LocationResult.class),
+ verify(listener, times(1)).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
mProvider.setAllowed(true);
@@ -430,7 +431,7 @@
loc = createLocationResult(NAME, mRandom);
mProvider.setProviderLocation(loc);
- verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class));
+ verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
}
@Test
@@ -447,7 +448,7 @@
LocationResult loc = createLocationResult(NAME, mRandom);
mProvider.setProviderLocation(loc);
- verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(eq(loc),
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(eq(loc.asList()),
nullable(IRemoteCallback.class));
}
@@ -462,7 +463,7 @@
mManager.unregisterLocationRequest(listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
- verify(listener, never()).onLocationChanged(any(LocationResult.class),
+ verify(listener, never()).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
@@ -493,7 +494,7 @@
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mManager.unregisterLocationRequest(listener);
blocker.countDown();
- verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(LocationResult.class),
+ verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
}
@@ -513,7 +514,7 @@
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
- verify(listener, times(5)).onLocationChanged(any(LocationResult.class),
+ verify(listener, times(5)).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
}
@@ -528,7 +529,7 @@
mInjector.getAlarmHelper().incrementAlarmTime(5000);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
- verify(listener, never()).onLocationChanged(any(LocationResult.class),
+ verify(listener, never()).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
}
@@ -544,7 +545,7 @@
Thread.sleep(25);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
- verify(listener, never()).onLocationChanged(any(LocationResult.class),
+ verify(listener, never()).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
}
@@ -561,7 +562,7 @@
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(1)).onLocationChanged(
- any(LocationResult.class), nullable(IRemoteCallback.class));
+ any(List.class), nullable(IRemoteCallback.class));
}
@Test
@@ -578,7 +579,7 @@
mProvider.setProviderLocation(loc);
verify(listener, times(1)).onLocationChanged(
- any(LocationResult.class), nullable(IRemoteCallback.class));
+ any(List.class), nullable(IRemoteCallback.class));
}
@Test
@@ -592,7 +593,7 @@
mProvider.setProviderLocation(createLocation(NAME, mRandom));
- verify(listener, never()).onLocationChanged(any(LocationResult.class),
+ verify(listener, never()).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
}
@@ -622,7 +623,7 @@
verify(mWakeLock, never()).release();
blocker.countDown();
- verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(LocationResult.class),
+ verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
verify(mWakeLock).acquire(anyLong());
verify(mWakeLock, timeout(TIMEOUT_MS)).release();
@@ -640,7 +641,7 @@
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(1))
- .onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class));
+ .onLocationChanged(any(List.class), nullable(IRemoteCallback.class));
}
@Test
@@ -657,7 +658,7 @@
mProvider.setProviderLocation(createLocation(NAME, mRandom));
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, times(1))
- .onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class));
+ .onLocationChanged(any(List.class), nullable(IRemoteCallback.class));
}
@Test
@@ -746,7 +747,7 @@
mProvider.completeFlushes();
InOrder inOrder = inOrder(listener);
- inOrder.verify(listener).onLocationChanged(eq(loc), any(IRemoteCallback.class));
+ inOrder.verify(listener).onLocationChanged(eq(loc.asList()), any(IRemoteCallback.class));
inOrder.verify(listener).onFlushComplete(99);
}
@@ -838,7 +839,7 @@
.build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
- verify(listener1).onLocationChanged(any(LocationResult.class),
+ verify(listener1).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
assertThat(mProvider.getRequest().isActive()).isFalse();
@@ -989,7 +990,7 @@
private ILocationListener createMockLocationListener() {
return spy(new ILocationListener.Stub() {
@Override
- public void onLocationChanged(LocationResult location,
+ public void onLocationChanged(List<Location> locations,
IRemoteCallback onCompleteCallback) {
if (onCompleteCallback != null) {
try {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
index 99846c5..07170da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
@@ -185,8 +185,8 @@
@Test
public void testReportLocation() {
- LocationResult realLocation = LocationResult.create(new Location("real"));
- LocationResult mockLocation = LocationResult.create(new Location("mock"));
+ LocationResult realLocation = LocationResult.wrap(new Location("real"));
+ LocationResult mockLocation = LocationResult.wrap(new Location("mock"));
mRealProvider.reportLocation(realLocation);
assertThat(mListener.getNextLocationResult()).isEqualTo(realLocation);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index c522541..6e27b3a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -64,6 +64,8 @@
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.parsing.pkg.ParsedPackage
import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
@@ -142,7 +144,7 @@
}
whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
- nullable(), nullable(), nullable())) {
+ nullable(), nullable(), nullable(), nullable())) {
val name: String = getArgument(0)
val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
?: return@whenever null
@@ -183,6 +185,8 @@
val dexManager: DexManager = mock()
val installer: Installer = mock()
val displayMetrics: DisplayMetrics = mock()
+ val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock()
+ val handler = TestHandler(null)
}
companion object {
@@ -258,6 +262,9 @@
whenever(mocks.injector.userManagerInternal).thenReturn(mocks.userManagerInternal)
whenever(mocks.injector.installer).thenReturn(mocks.installer)
whenever(mocks.injector.displayMetrics).thenReturn(mocks.displayMetrics)
+ whenever(mocks.injector.domainVerificationManagerInternal)
+ .thenReturn(mocks.domainVerificationManagerInternal)
+ whenever(mocks.injector.handler) { mocks.handler }
wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index 0a35db5..f7b2492 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -16,34 +16,61 @@
package com.android.server;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
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.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
import android.hardware.vibrator.IVibrator;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.os.CombinedVibrationEffect;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.IVibratorStateListener;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.vibrator.FakeVibrator;
import com.android.server.vibrator.FakeVibratorControllerProvider;
import com.android.server.vibrator.VibratorController;
@@ -51,12 +78,16 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Predicate;
/**
* Tests for {@link VibratorManagerService}.
@@ -67,39 +98,86 @@
@Presubmit
public class VibratorManagerServiceTest {
+ private static final int TEST_TIMEOUT_MILLIS = 1_000;
private static final int UID = Process.ROOT_UID;
private static final String PACKAGE_NAME = "package";
+ private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
+ private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
+ .setBatterySaverEnabled(true).build();
private static final VibrationAttributes ALARM_ATTRS =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
+ private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_TOUCH).build();
+ private static final VibrationAttributes NOTIFICATION_ATTRS =
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_NOTIFICATION).build();
+ private static final VibrationAttributes RINGTONE_ATTRS =
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_RINGTONE).build();
@Rule public MockitoRule rule = MockitoJUnit.rule();
+ @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+ @Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private PowerManagerInternal mPowerManagerInternalMock;
@Mock private PowerSaveState mPowerSaveStateMock;
+ @Mock private AppOpsManager mAppOpsManagerMock;
+ @Mock private IInputManager mIInputManagerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
+ private Context mContextSpy;
private TestLooper mTestLooper;
+ private FakeVibrator mVibrator;
+ private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
+ mVibrator = new FakeVibrator();
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+ ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+ when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+ when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
+ when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
+ when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+ when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+ .thenReturn(new ComponentName("", ""));
when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
.thenReturn(mPowerSaveStateMock);
+ doAnswer(invocation -> {
+ mRegisteredPowerModeListener = invocation.getArgument(0);
+ return null;
+ }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+
+ addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+ mTestLooper.startAutoDispatch();
}
@After
public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(PowerManagerInternal.class);
}
private VibratorManagerService createService() {
VibratorManagerService service = new VibratorManagerService(
- InstrumentationRegistry.getContext(),
+ mContextSpy,
new VibratorManagerService.Injector() {
@Override
VibratorManagerService.NativeWrapper getNativeWrapper() {
@@ -172,6 +250,74 @@
}
@Test
+ public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+ IVibratorStateListener listenerMock = mockVibratorStateListener();
+ service.registerVibratorStateListener(1, listenerMock);
+
+ vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+ // Wait until service knows vibrator is on.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ // Wait until effect ends.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ InOrder inOrderVerifier = inOrder(listenerMock);
+ // First notification done when listener is registered.
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ inOrderVerifier.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+ IVibratorStateListener listenerMock = mockVibratorStateListener();
+ service.registerVibratorStateListener(1, listenerMock);
+
+ vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+
+ // Wait until service knows vibrator is on.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.unregisterVibratorStateListener(1, listenerMock);
+
+ // Wait until vibrator is off.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ InOrder inOrderVerifier = inOrder(listenerMock);
+ // First notification done when listener is registered.
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+ inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
+ inOrderVerifier.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
+ mockVibrators(0, 1, 2);
+ VibratorManagerService service = createService();
+ IVibratorStateListener[] listeners = new IVibratorStateListener[3];
+ for (int i = 0; i < 3; i++) {
+ listeners[i] = mockVibratorStateListener();
+ service.registerVibratorStateListener(i, listeners[i]);
+ }
+
+ vibrate(service, CombinedVibrationEffect.startSynced()
+ .addVibrator(0, VibrationEffect.createOneShot(40, 100))
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .combine(), ALARM_ATTRS);
+ // Wait until service knows vibrator is on.
+ assertTrue(waitUntil(s -> s.isVibrating(0), service, TEST_TIMEOUT_MILLIS));
+
+ verify(listeners[0]).onVibrating(eq(true));
+ verify(listeners[1]).onVibrating(eq(true));
+ verify(listeners[2], never()).onVibrating(eq(true));
+ }
+
+ @Test
public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
@@ -276,22 +422,264 @@
}
@Test
- public void vibrate_isUnsupported() {
+ public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
+ mockVibrators(1);
+ mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
VibratorManagerService service = createService();
- CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
- VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- assertExpectException(UnsupportedOperationException.class,
- "Not implemented",
- () -> service.vibrate(UID, PACKAGE_NAME, effect, ALARM_ATTRS, "reason", service));
+ vibrate(service, VibrationEffect.createOneShot(40, 1), RINGTONE_ATTRS);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ service = createService();
+ vibrate(service, VibrationEffect.createOneShot(40, 10), RINGTONE_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ service = createService();
+ vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ assertEquals(2, mVibratorProviders.get(1).getEffects().size());
+ assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
}
@Test
- public void cancelVibrate_isUnsupported() {
+ public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createService();
- CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
- VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- assertExpectException(UnsupportedOperationException.class,
- "Not implemented", () -> service.cancelVibrate(service));
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+ vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+ vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ service, TEST_TIMEOUT_MILLIS));
+
+ assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
+ }
+
+ @Test
+ public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
+ VibratorManagerService service = createService();
+
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+ VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
+ audioAttributes, effect).build();
+
+ vibrate(service, effect, vibrationAttributes);
+
+ verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
+ }
+
+ @Test
+ public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
+ VibratorManagerService service = createService();
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_UNKNOWN).build());
+
+ InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
+ anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+ }
+
+ @Test
+ public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
+ setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
+ VibratorManagerService service = createService();
+
+ // Prebaked vibration will play fallback waveform on input device.
+ ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+ verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
+ assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
+
+ VibrationEffect[] effects = new VibrationEffect[]{
+ VibrationEffect.createOneShot(100, 128),
+ VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose(),
+ };
+
+ for (VibrationEffect effect : effects) {
+ vibrate(service, effect, ALARM_ATTRS);
+ verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
+ }
+
+ // VibrationThread will start this vibration async, so wait before checking it never played.
+ assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
+ service, /* timeout= */ 50));
+ }
+
+ @Test
+ public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
+ IVibrator.CAP_AMPLITUDE_CONTROL);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createService();
+ // The native callback will be dispatched manually in this test.
+ mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait before triggering callbacks.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ // Trigger callbacks from controller.
+ mTestLooper.moveTimeForward(50);
+ mTestLooper.dispatchAll();
+
+ // VibrationThread needs some time to react to native callbacks and stop the vibrator.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ }
+
+
+ @Test
+ public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
+ mVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_OFF);
+
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+ IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createService();
+
+ vibrate(service, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .combine(), ALARM_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.createOneShot(20, 100))
+ .combine(), NOTIFICATION_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .compose(), HAPTIC_FEEDBACK_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+
+ assertEquals(3, fakeVibrator.getEffects().size());
+ assertEquals(1, fakeVibrator.getAmplitudes().size());
+
+ // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
+ VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
+ VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertEquals(expected, fakeVibrator.getEffects().get(0));
+
+ // Notification vibrations will be scaled with SCALE_VERY_HIGH.
+ assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
+
+ // Haptic feedback vibrations will be scaled with SCALE_LOW.
+ VibrationEffect.Composed played =
+ (VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
+ assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
+ assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
+
+ // Ring vibrations have intensity OFF and are not played.
+ }
+
+ @Test
+ public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
+ mockVibrators(1, 2);
+ VibratorManagerService service = createService();
+ vibrate(service,
+ CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+ .combine(),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ // Haptic feedback cancelled on low power mode.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ }
+
+ @Test
+ public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+
+ vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.updateServiceState();
+ // Vibration is not stopped nearly after updating service.
+ assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+ }
+
+ @Test
+ public void cancelVibrate_stopsVibrating() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+
+ service.cancelVibrate(service);
+ assertFalse(service.isVibrating(1));
+
+ vibrate(service, VibrationEffect.createOneShot(10_000, 100), ALARM_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.cancelVibrate(service);
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
private void mockVibrators(int... vibratorIds) {
@@ -302,8 +690,56 @@
when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
}
+ private IVibratorStateListener mockVibratorStateListener() {
+ IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
+ IBinder binderMock = mock(IBinder.class);
+ when(listenerMock.asBinder()).thenReturn(binderMock);
+ return listenerMock;
+ }
+
+ private InputDevice createInputDeviceWithVibrator(int id) {
+ return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
+ null, /* hasVibrator= */ true, false, false, false, false);
+ }
+
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
}
+
+ private void setRingerMode(int ringerMode) {
+ AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
+ audioManager.setRingerModeInternal(ringerMode);
+ assertEquals(ringerMode, audioManager.getRingerModeInternal());
+ }
+
+ private void setUserSetting(String settingName, int value) {
+ Settings.System.putIntForUser(
+ mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+ }
+
+ private void setGlobalSetting(String settingName, int value) {
+ Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
+ }
+
+ private void vibrate(VibratorManagerService service, VibrationEffect effect,
+ VibrationAttributes attrs) {
+ vibrate(service, CombinedVibrationEffect.createSynced(effect), attrs);
+ }
+
+ private void vibrate(VibratorManagerService service, CombinedVibrationEffect effect,
+ VibrationAttributes attrs) {
+ service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+ }
+
+ private boolean waitUntil(Predicate<VibratorManagerService> predicate,
+ VibratorManagerService service, long timeout) throws InterruptedException {
+ long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
+ boolean predicateResult = false;
+ while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
+ Thread.sleep(10);
+ predicateResult = predicate.test(service);
+ }
+ return predicateResult;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 743848c..2a7905a 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -582,16 +582,19 @@
VibratorService service = createService();
service.registerVibratorStateListener(mVibratorStateListenerMock);
- verify(mVibratorStateListenerMock).onVibrating(false);
- vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
+ vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before triggering callbacks.
Thread.sleep(10);
+ assertTrue(service.isVibrating());
+
service.unregisterVibratorStateListener(mVibratorStateListenerMock);
// Trigger callbacks from controller.
- mTestLooper.moveTimeForward(150);
+ mTestLooper.moveTimeForward(50);
mTestLooper.dispatchAll();
+ Thread.sleep(20);
+ assertFalse(service.isVibrating());
InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
// First notification done when listener is registered.
@@ -745,7 +748,8 @@
private InputDevice createInputDeviceWithVibrator(int id) {
return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
- null, /* hasVibrator= */ true, false, false, false /* hasSensor */);
+ null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
+ false /* hasBattery */);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index 3b4699e..8c21a39 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -134,7 +134,7 @@
private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
long lastNetworkUpdatedProcStateSeq) {
- final UidRecord record = new UidRecord(uid);
+ final UidRecord record = new UidRecord(uid, mAms);
record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
record.curProcStateSeq = curProcStateSeq;
record.waitingForNetwork = true;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index e119d52..f314008 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -273,7 +273,7 @@
}
private UidRecord addUidRecord(int uid) {
- final UidRecord uidRec = new UidRecord(uid);
+ final UidRecord uidRec = new UidRecord(uid, mAms);
uidRec.waitingForNetwork = true;
uidRec.hasInternetPermission = true;
mAms.mProcessList.mActiveUids.put(uid, uidRec);
@@ -282,8 +282,9 @@
info.packageName = "";
final ProcessRecord appRec = new ProcessRecord(mAms, info, TAG, uid);
- appRec.thread = mock(IApplicationThread.class);
- mAms.mProcessList.mLruProcesses.add(appRec);
+ final ProcessStatsService tracker = new ProcessStatsService(mAms, mContext.getCacheDir());
+ appRec.makeActive(mock(IApplicationThread.class), tracker);
+ mAms.mProcessList.getLruProcessesLSP().add(appRec);
return uidRec;
}
@@ -295,23 +296,23 @@
CustomThread thread = new CustomThread(uidRec.networkStateLock);
thread.startAndWait("Unexpected state for " + uidRec);
- uidRec.setProcState = prevState;
+ uidRec.setSetProcState(prevState);
uidRec.setCurProcState(curState);
- mAms.mProcessList.incrementProcStateSeqAndNotifyAppsLocked(mAms.mProcessList.mActiveUids);
+ mAms.mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(mAms.mProcessList.mActiveUids);
// @SuppressWarnings("GuardedBy")
assertEquals(expectedGlobalCounter, mAms.mProcessList.mProcStateSeqCounter);
assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq);
- for (int i = mAms.mProcessList.getLruSizeLocked() - 1; i >= 0; --i) {
- final ProcessRecord app = mAms.mProcessList.mLruProcesses.get(i);
+ for (int i = mAms.mProcessList.getLruSizeLOSP() - 1; i >= 0; --i) {
+ final ProcessRecord app = mAms.mProcessList.getLruProcessesLOSP().get(i);
// AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE.
- if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) {
- verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq);
+ if (app.uid == uidRec.getUid() && expectedBlockState == NETWORK_STATE_BLOCK) {
+ verify(app.getThread()).setNetworkBlockSeq(uidRec.curProcStateSeq);
} else {
- verifyZeroInteractions(app.thread);
+ verifyZeroInteractions(app.getThread());
}
- Mockito.reset(app.thread);
+ Mockito.reset(app.getThread());
}
if (expectNotify) {
@@ -446,55 +447,56 @@
@Test
public void testBlockStateForUid() {
- final UidRecord uidRec = new UidRecord(TEST_UID);
+ final UidRecord uidRec = new UidRecord(TEST_UID, mAms);
int expectedBlockState;
final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s";
Function<Integer, String> errorMsg = (blockState) -> {
return String.format(errorTemplate,
valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState),
- valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState),
+ valueToString(ActivityManager.class, "PROCESS_STATE_",
+ uidRec.getSetProcState()),
valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.getCurProcState())
);
};
// No change in uid state
- uidRec.setProcState = PROCESS_STATE_RECEIVER;
+ uidRec.setSetProcState(PROCESS_STATE_RECEIVER);
uidRec.setCurProcState(PROCESS_STATE_RECEIVER);
expectedBlockState = NETWORK_STATE_NO_CHANGE;
assertEquals(errorMsg.apply(expectedBlockState),
expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
// Foreground to foreground
- uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+ uidRec.setSetProcState(PROCESS_STATE_FOREGROUND_SERVICE);
uidRec.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
expectedBlockState = NETWORK_STATE_NO_CHANGE;
assertEquals(errorMsg.apply(expectedBlockState),
expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
// Background to background
- uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY;
+ uidRec.setSetProcState(PROCESS_STATE_CACHED_ACTIVITY);
uidRec.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
expectedBlockState = NETWORK_STATE_NO_CHANGE;
assertEquals(errorMsg.apply(expectedBlockState),
expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
// Background to background
- uidRec.setProcState = PROCESS_STATE_NONEXISTENT;
+ uidRec.setSetProcState(PROCESS_STATE_NONEXISTENT);
uidRec.setCurProcState(PROCESS_STATE_CACHED_ACTIVITY);
expectedBlockState = NETWORK_STATE_NO_CHANGE;
assertEquals(errorMsg.apply(expectedBlockState),
expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
// Background to foreground
- uidRec.setProcState = PROCESS_STATE_SERVICE;
+ uidRec.setSetProcState(PROCESS_STATE_SERVICE);
uidRec.setCurProcState(PROCESS_STATE_FOREGROUND_SERVICE);
expectedBlockState = NETWORK_STATE_BLOCK;
assertEquals(errorMsg.apply(expectedBlockState),
expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
// Foreground to background
- uidRec.setProcState = PROCESS_STATE_TOP;
+ uidRec.setSetProcState(PROCESS_STATE_TOP);
uidRec.setCurProcState(PROCESS_STATE_LAST_ACTIVITY);
expectedBlockState = NETWORK_STATE_UNBLOCK;
assertEquals(errorMsg.apply(expectedBlockState),
@@ -748,13 +750,13 @@
item.procState, validateUidRecord.getCurProcState());
assertEquals("processState: " + item.procState + " setProcState: "
+ validateUidRecord.getCurProcState() + " should have been equal",
- item.procState, validateUidRecord.setProcState);
+ item.procState, validateUidRecord.getSetProcState());
if (item.change == UidRecord.CHANGE_IDLE) {
assertTrue("UidRecord.idle should be updated to true for CHANGE_IDLE",
- validateUidRecord.idle);
+ validateUidRecord.isIdle());
} else if (item.change == UidRecord.CHANGE_ACTIVE) {
assertFalse("UidRecord.idle should be updated to false for CHANGE_ACTIVE",
- validateUidRecord.idle);
+ validateUidRecord.isIdle());
}
}
}
@@ -779,7 +781,7 @@
@Test
public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
- final UidRecord uidRecord = new UidRecord(TEST_UID);
+ final UidRecord uidRecord = new UidRecord(TEST_UID, mAms);
uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
// Verify with no pending changes for TEST_UID.
@@ -823,9 +825,9 @@
@MediumTest
@Test
public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
- final UidRecord uidRecord = new UidRecord(TEST_UID);
+ final UidRecord uidRecord = new UidRecord(TEST_UID, mAms);
final int expectedProcState = PROCESS_STATE_SERVICE;
- uidRecord.setProcState = expectedProcState;
+ uidRecord.setSetProcState(expectedProcState);
uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
// Test with no pending uid records.
@@ -895,7 +897,7 @@
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {
- final UidRecord record = new UidRecord(Process.myUid());
+ final UidRecord record = new UidRecord(Process.myUid(), mAms);
record.curProcStateSeq = curProcStateSeq;
record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index 52c824a..432f6d6 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -33,6 +33,8 @@
import org.junit.Before;
import org.junit.Test;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit;
/**
@@ -48,7 +50,26 @@
@Before
public void setUp() {
- runWithDexmakerShareClassLoader(() -> mAnrApp = mock(ProcessRecord.class));
+ runWithDexmakerShareClassLoader(() -> {
+ mAnrApp = mock(ProcessRecord.class);
+ final ActivityManagerService service = mock(ActivityManagerService.class);
+ final ProcessErrorStateRecord errorState = mock(ProcessErrorStateRecord.class);
+ setFieldValue(ProcessErrorStateRecord.class, errorState, "mProcLock",
+ new ActivityManagerProcLock());
+ setFieldValue(ProcessRecord.class, mAnrApp, "mErrorState", errorState);
+ });
+ }
+
+ private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Field mfield = Field.class.getDeclaredField("accessFlags");
+ mfield.setAccessible(true);
+ mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+ field.set(obj, val);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ }
}
@Test
@@ -62,7 +83,7 @@
mAnrHelper.appNotResponding(mAnrApp, activityShortComponentName, appInfo,
parentShortComponentName, parentProcess, aboveSystem, annotation);
- verify(mAnrApp, timeout(TimeUnit.SECONDS.toMillis(5))).appNotResponding(
+ verify(mAnrApp.mErrorState, timeout(TimeUnit.SECONDS.toMillis(5))).appNotResponding(
eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
eq(parentProcess), eq(aboveSystem), eq(annotation), eq(false) /* onlyDumpSelf */);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
new file mode 100644
index 0000000..fdf5095
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.hardware.power.stats.Channel;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntity;
+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;
+
+/**
+ * Tests for {@link BatteryExternalStatsWorker}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:BatteryExternalStatsWorkerTest
+ */
+public class BatteryExternalStatsWorkerTest {
+ private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
+ private TestBatteryStatsImpl mBatteryStatsImpl;
+ private TestPowerStatsInternal mPowerStatsInternal;
+
+ @Before
+ public void setUp() {
+ final Context context = InstrumentationRegistry.getContext();
+
+ mBatteryStatsImpl = new TestBatteryStatsImpl();
+ mPowerStatsInternal = new TestPowerStatsInternal();
+ mBatteryExternalStatsWorker = new BatteryExternalStatsWorker(new TestInjector(context),
+ 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);
+ }
+
+ public <T> T getSystemService(Class<T> serviceClass) {
+ return null;
+ }
+
+ public <T> T getLocalService(Class<T> serviceClass) {
+ if (serviceClass == PowerStatsInternal.class) {
+ return (T) mPowerStatsInternal;
+ }
+ return null;
+ }
+ }
+
+ public class TestBatteryStatsImpl extends BatteryStatsImpl {
+ }
+
+ public class TestPowerStatsInternal extends PowerStatsInternal {
+ private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>();
+ private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults =
+ new SparseArray<>();
+ private final int mTimeSinceBoot = 0;
+
+ @Override
+ public EnergyConsumer[] getEnergyConsumerInfo() {
+ final int size = mEnergyConsumers.size();
+ final EnergyConsumer[] consumers = new EnergyConsumer[size];
+ for (int i = 0; i < size; i++) {
+ consumers[i] = mEnergyConsumers.valueAt(i);
+ }
+ return consumers;
+ }
+
+ @Override
+ public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
+ int[] energyConsumerIds) {
+ final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture();
+ final int size = mEnergyConsumerResults.size();
+ final EnergyConsumerResult[] results = new EnergyConsumerResult[size];
+ for (int i = 0; i < size; i++) {
+ results[i] = mEnergyConsumerResults.valueAt(i);
+ }
+ future.complete(results);
+ return future;
+ }
+
+ @Override
+ public PowerEntity[] getPowerEntityInfo() {
+ return new PowerEntity[0];
+ }
+
+ @Override
+ public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
+ int[] powerEntityIds) {
+ return new CompletableFuture<>();
+ }
+
+ @Override
+ public Channel[] getEnergyMeterInfo() {
+ return new Channel[0];
+ }
+
+ @Override
+ public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
+ int[] channelIds) {
+ return new CompletableFuture<>();
+ }
+
+ /**
+ * Util method to add a new EnergyConsumer for testing
+ *
+ * @return the EnergyConsumer id of the new EnergyConsumer
+ */
+ public int addEnergyConsumer(@EnergyConsumerType byte type, int ordinal, String name) {
+ final EnergyConsumer consumer = new EnergyConsumer();
+ final int id = getNextAvailableId();
+ consumer.id = id;
+ consumer.type = type;
+ consumer.ordinal = ordinal;
+ consumer.name = name;
+ mEnergyConsumers.put(id, consumer);
+
+ final EnergyConsumerResult result = new EnergyConsumerResult();
+ result.id = id;
+ result.timestampMs = mTimeSinceBoot;
+ result.energyUWs = 0;
+ mEnergyConsumerResults.put(id, result);
+ return id;
+ }
+
+ public void incrementEnergyConsumption(int id, long energyUWs) {
+ EnergyConsumerResult result = mEnergyConsumerResults.get(id, null);
+ assertNotNull(result);
+ result.energyUWs += energyUWs;
+ }
+
+ private int getNextAvailableId() {
+ final int size = mEnergyConsumers.size();
+ // Just return the first index that does not match the key (aka the EnergyConsumer id)
+ for (int i = size - 1; i >= 0; i--) {
+ if (mEnergyConsumers.keyAt(i) == i) return i + 1;
+ }
+ // Otherwise return the lowest id
+ return 0;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 7355b80..638b1b4 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -77,6 +77,8 @@
sService.mActivityTaskManager.initialize(null, null, sContext.getMainLooper());
sService.mAtmInternal = sService.mActivityTaskManager.getAtmInternal();
+ setFieldValue(ActivityManagerService.class, sService, "mProcLock",
+ new ActivityManagerProcLock());
sService.mConstants = new ActivityManagerConstants(sContext, sService,
sContext.getMainThreadHandler());
final AppProfiler profiler = mock(AppProfiler.class);
@@ -124,7 +126,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStatePersistentUI() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, elapsedTime);
@@ -133,7 +135,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateTop() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, elapsedTime);
@@ -142,8 +144,8 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateTop_PreviousInteraction() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
- mProcessRecord.reportedInteraction = true;
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
+ mProcessRecord.mState.setReportedInteraction(true);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, ZERO);
@@ -152,8 +154,8 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateTop_PastUsageInterval() {
final long elapsedTime = 3 * USAGE_STATS_INTERACTION;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
- mProcessRecord.reportedInteraction = true;
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
+ mProcessRecord.mState.setReportedInteraction(true);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, elapsedTime);
@@ -162,7 +164,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateBoundTop() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_TOP);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_TOP);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, elapsedTime);
@@ -171,7 +173,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateFGS() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(elapsedTime, false, ZERO);
@@ -181,8 +183,8 @@
public void testMaybeUpdateUsageStats_ProcStateFGS_ShortInteraction() {
final long elapsedTime = ZERO;
final long fgInteractionTime = 1000L;
- mProcessRecord.setFgInteractionTime(fgInteractionTime);
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mProcessRecord.mState.setFgInteractionTime(fgInteractionTime);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(fgInteractionTime, false, ZERO);
@@ -192,8 +194,8 @@
public void testMaybeUpdateUsageStats_ProcStateFGS_LongInteraction() {
final long elapsedTime = 2 * SERVICE_USAGE_INTERACTION;
final long fgInteractionTime = 1000L;
- mProcessRecord.setFgInteractionTime(fgInteractionTime);
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mProcessRecord.mState.setFgInteractionTime(fgInteractionTime);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(fgInteractionTime, true, elapsedTime);
@@ -203,9 +205,9 @@
public void testMaybeUpdateUsageStats_ProcStateFGS_PreviousLongInteraction() {
final long elapsedTime = 2 * SERVICE_USAGE_INTERACTION;
final long fgInteractionTime = 1000L;
- mProcessRecord.setFgInteractionTime(fgInteractionTime);
- mProcessRecord.reportedInteraction = true;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mProcessRecord.mState.setFgInteractionTime(fgInteractionTime);
+ mProcessRecord.mState.setReportedInteraction(true);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(fgInteractionTime, true, ZERO);
@@ -214,7 +216,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateFGSLocation() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(elapsedTime, false, ZERO);
@@ -223,7 +225,8 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateBFGS() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ mProcessRecord.mState.setCurProcState(
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, elapsedTime);
@@ -232,7 +235,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateImportantFG() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, elapsedTime);
@@ -241,8 +244,8 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateImportantFG_PreviousInteraction() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
- mProcessRecord.reportedInteraction = true;
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mProcessRecord.mState.setReportedInteraction(true);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, ZERO);
@@ -251,8 +254,8 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateImportantFG_PastUsageInterval() {
final long elapsedTime = 3 * USAGE_STATS_INTERACTION;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
- mProcessRecord.reportedInteraction = true;
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mProcessRecord.mState.setReportedInteraction(true);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, true, elapsedTime);
@@ -261,7 +264,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateImportantBG() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, false, ZERO);
@@ -270,7 +273,7 @@
@Test
public void testMaybeUpdateUsageStats_ProcStateService() {
final long elapsedTime = ZERO;
- mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_SERVICE);
+ mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_SERVICE);
sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
assertProcessRecordState(ZERO, false, ZERO);
@@ -279,10 +282,10 @@
private void assertProcessRecordState(long fgInteractionTime, boolean reportedInteraction,
long interactionEventTime) {
assertEquals("Foreground interaction time was not updated correctly.",
- fgInteractionTime, mProcessRecord.getFgInteractionTime());
+ fgInteractionTime, mProcessRecord.mState.getFgInteractionTime());
assertEquals("Interaction was not updated correctly.",
- reportedInteraction, mProcessRecord.reportedInteraction);
+ reportedInteraction, mProcessRecord.mState.hasReportedInteraction());
assertEquals("Interaction event time was not updated correctly.",
- interactionEventTime, mProcessRecord.getInteractionEventTime());
+ interactionEventTime, mProcessRecord.mState.getInteractionEventTime());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index 263efa6..6538a17 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -44,7 +44,6 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-import java.util.Collections;
/**
* Build/Install/Run:
@@ -57,6 +56,7 @@
private static ActivityManagerService sService;
private ProcessRecord mProcessRecord;
+ private ProcessErrorStateRecord mProcessErrorState;
@BeforeClass
public static void setUpOnce() throws Exception {
@@ -72,6 +72,8 @@
final AppProfiler profiler = mock(AppProfiler.class);
setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
+ setFieldValue(ActivityManagerService.class, sService, "mProcLock",
+ new ActivityManagerProcLock());
final ProcessList processList = new ProcessList();
setFieldValue(ActivityManagerService.class, sService, "mProcessList", processList);
});
@@ -104,12 +106,12 @@
public void setUpProcess() throws Exception {
// Need to run with dexmaker share class loader to mock package private class.
runWithDexmakerShareClassLoader(() -> {
- mProcessRecord = spy(new ProcessRecord(sService, sContext.getApplicationInfo(),
- "name", 12345));
- doNothing().when(mProcessRecord).startAppProblemLocked();
- doReturn(false).when(mProcessRecord).isSilentAnr();
- doReturn(false).when(mProcessRecord).isMonitorCpuUsage();
- doReturn(Collections.emptyList()).when(mProcessRecord).getLruProcessList();
+ mProcessRecord = new ProcessRecord(sService, sContext.getApplicationInfo(),
+ "name", 12345);
+ mProcessErrorState = spy(mProcessRecord.mErrorState);
+ doNothing().when(mProcessErrorState).startAppProblemLSP();
+ doReturn(false).when(mProcessErrorState).isSilentAnr();
+ doReturn(false).when(mProcessErrorState).isMonitorCpuUsage();
});
}
@@ -120,10 +122,10 @@
*/
@Test
public void testProcessDefaultAnrRelatedStatus() {
- assertFalse(mProcessRecord.isNotResponding());
- assertFalse(mProcessRecord.isCrashing());
- assertFalse(mProcessRecord.killedByAm);
- assertFalse(mProcessRecord.killed);
+ assertFalse(mProcessErrorState.isNotResponding());
+ assertFalse(mProcessErrorState.isCrashing());
+ assertFalse(mProcessRecord.isKilledByAm());
+ assertFalse(mProcessRecord.isKilled());
}
/**
@@ -131,12 +133,12 @@
*/
@Test
public void testAnrWhenCrash() {
- mProcessRecord.setCrashing(true);
- assertTrue(mProcessRecord.isCrashing());
- appNotResponding(mProcessRecord, "Test ANR when crash");
- assertFalse(mProcessRecord.isNotResponding());
- assertFalse(mProcessRecord.killedByAm);
- assertFalse(mProcessRecord.killed);
+ mProcessErrorState.setCrashing(true);
+ assertTrue(mProcessErrorState.isCrashing());
+ appNotResponding(mProcessErrorState, "Test ANR when crash");
+ assertFalse(mProcessErrorState.isNotResponding());
+ assertFalse(mProcessRecord.isKilledByAm());
+ assertFalse(mProcessRecord.isKilled());
}
/**
@@ -144,11 +146,11 @@
*/
@Test
public void testAnrWhenKilledByAm() {
- mProcessRecord.killedByAm = true;
- appNotResponding(mProcessRecord, "Test ANR when killed by AM");
- assertFalse(mProcessRecord.isNotResponding());
- assertFalse(mProcessRecord.isCrashing());
- assertFalse(mProcessRecord.killed);
+ mProcessRecord.setKilledByAm(true);
+ appNotResponding(mProcessErrorState, "Test ANR when killed by AM");
+ assertFalse(mProcessErrorState.isNotResponding());
+ assertFalse(mProcessErrorState.isCrashing());
+ assertFalse(mProcessRecord.isKilled());
}
/**
@@ -156,11 +158,11 @@
*/
@Test
public void testAnrWhenKilled() {
- mProcessRecord.killed = true;
- appNotResponding(mProcessRecord, "Test ANR when killed");
- assertFalse(mProcessRecord.isNotResponding());
- assertFalse(mProcessRecord.isCrashing());
- assertFalse(mProcessRecord.killedByAm);
+ mProcessRecord.setKilled(true);
+ appNotResponding(mProcessErrorState, "Test ANR when killed");
+ assertFalse(mProcessErrorState.isNotResponding());
+ assertFalse(mProcessErrorState.isCrashing());
+ assertFalse(mProcessRecord.isKilledByAm());
}
/**
@@ -169,11 +171,11 @@
*/
@Test
public void testNonSilentAnr() {
- appNotResponding(mProcessRecord, "Test non-silent ANR");
- assertTrue(mProcessRecord.isNotResponding());
- assertFalse(mProcessRecord.isCrashing());
- assertFalse(mProcessRecord.killedByAm);
- assertFalse(mProcessRecord.killed);
+ appNotResponding(mProcessErrorState, "Test non-silent ANR");
+ assertTrue(mProcessErrorState.isNotResponding());
+ assertFalse(mProcessErrorState.isCrashing());
+ assertFalse(mProcessRecord.isKilledByAm());
+ assertFalse(mProcessRecord.isKilled());
}
/**
@@ -183,16 +185,17 @@
@Test
public void testSilentAnr() {
// Silent Anr will run through even without a parent process, and directly killed by AM.
- doReturn(true).when(mProcessRecord).isSilentAnr();
- appNotResponding(mProcessRecord, "Test silent ANR");
- assertTrue(mProcessRecord.isNotResponding());
- assertFalse(mProcessRecord.isCrashing());
- assertTrue(mProcessRecord.killedByAm);
- assertTrue(mProcessRecord.killed);
+ doReturn(true).when(mProcessErrorState).isSilentAnr();
+ appNotResponding(mProcessErrorState, "Test silent ANR");
+ assertTrue(mProcessErrorState.isNotResponding());
+ assertFalse(mProcessErrorState.isCrashing());
+ assertTrue(mProcessRecord.isKilledByAm());
+ assertTrue(mProcessRecord.isKilled());
}
- private static void appNotResponding(ProcessRecord processRecord, String annotation) {
- processRecord.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
+ private static void appNotResponding(ProcessErrorStateRecord processErrorState,
+ String annotation) {
+ processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
null /* parentShortComponentName */, null /* parentProcess */,
false /* aboveSystem */, annotation, false /* onlyDumpSelf */);
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java
rename to services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 6fc6b9e..738f008 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java
rename to services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
index 22df645..caa46da 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
import static org.junit.Assert.assertEquals;
-import android.graphics.GameManager;
+import android.app.GameManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 45bca68..1328b91 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -16,16 +16,18 @@
package com.android.server.apphibernation;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
-import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
@@ -48,6 +50,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -76,18 +79,21 @@
private IActivityManager mIActivityManager;
@Mock
private UserManager mUserManager;
+ @Mock
+ private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore;
@Captor
private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
@Before
public void setUp() throws RemoteException {
+ // Share class loader to allow access to package-private classes
+ System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
- mAppHibernationService = new AppHibernationService(mContext, mIPackageManager,
- mIActivityManager, mUserManager);
+ mAppHibernationService = new AppHibernationService(new MockInjector(mContext));
- verify(mContext, times(2)).registerReceiver(mReceiverCaptor.capture(), any());
+ verify(mContext).registerReceiver(mReceiverCaptor.capture(), any());
mBroadcastReceiver = mReceiverCaptor.getValue();
doReturn(mUserInfos).when(mUserManager).getUsers();
@@ -95,12 +101,19 @@
doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(),
anyInt(), anyBoolean(), anyBoolean(), any(), any());
- addUser(USER_ID_1);
+ List<PackageInfo> packages = new ArrayList<>();
+ packages.add(makePackageInfo(PACKAGE_NAME_1));
+ doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
+ intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt());
mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ UserInfo userInfo = addUser(USER_ID_1);
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
+ doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1);
}
@Test
- public void testSetHibernatingForUser_packageIsHibernating() throws RemoteException {
+ public void testSetHibernatingForUser_packageIsHibernating() {
// WHEN we hibernate a package for a user
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
@@ -109,8 +122,7 @@
}
@Test
- public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating()
- throws RemoteException {
+ public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() {
// WHEN a new package is added and it is hibernated
Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED,
Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */));
@@ -124,17 +136,12 @@
}
@Test
- public void testSetHibernatingForUser_newUserAdded_packageIsHibernating()
+ public void testSetHibernatingForUser_newUserUnlocked_packageIsHibernating()
throws RemoteException {
// WHEN a new user is added and a package from the user is hibernated
- List<PackageInfo> userPackages = new ArrayList<>();
- userPackages.add(makePackageInfo(PACKAGE_NAME_1));
- doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager)
- .getInstalledPackages(anyInt(), eq(USER_ID_2));
- Intent intent = new Intent(Intent.ACTION_USER_ADDED);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_2);
- mBroadcastReceiver.onReceive(mContext, intent);
-
+ UserInfo user2 = addUser(USER_ID_2);
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
+ doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
// THEN the new user's package is hibernated
@@ -142,8 +149,7 @@
}
@Test
- public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating()
- throws RemoteException {
+ public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() {
// GIVEN a package is currently hibernated
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
@@ -168,25 +174,25 @@
}
/**
- * Add a mock user with one package. Must be called before
- * {@link AppHibernationService#onBootPhase(int)} to work properly.
+ * Add a mock user with one package.
*/
- private void addUser(int userId) throws RemoteException {
- addUser(userId, new String[]{PACKAGE_NAME_1});
+ private UserInfo addUser(int userId) throws RemoteException {
+ return addUser(userId, new String[]{PACKAGE_NAME_1});
}
/**
- * Add a mock user with the packages specified. Must be called before
- * {@link AppHibernationService#onBootPhase(int)} to work properly
+ * Add a mock user with the packages specified.
*/
- private void addUser(int userId, String[] packageNames) throws RemoteException {
- mUserInfos.add(new UserInfo(userId, "user_" + userId, 0 /* flags */));
+ private UserInfo addUser(int userId, String[] packageNames) throws RemoteException {
+ UserInfo userInfo = new UserInfo(userId, "user_" + userId, 0 /* flags */);
+ mUserInfos.add(userInfo);
List<PackageInfo> userPackages = new ArrayList<>();
for (String pkgName : packageNames) {
userPackages.add(makePackageInfo(pkgName));
}
doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager)
- .getInstalledPackages(anyInt(), eq(userId));
+ .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId));
+ return userInfo;
}
private static PackageInfo makePackageInfo(String packageName) {
@@ -194,4 +200,42 @@
pkg.packageName = packageName;
return pkg;
}
+
+ private class MockInjector implements AppHibernationService.Injector {
+ private final Context mContext;
+
+ MockInjector(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public IActivityManager getActivityManager() {
+ return mIActivityManager;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public IPackageManager getPackageManager() {
+ return mIPackageManager;
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
+ return Mockito.mock(HibernationStateDiskStore.class);
+ }
+
+ @Override
+ public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
+ return Mockito.mock(HibernationStateDiskStore.class);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java
new file mode 100644
index 0000000..59f3c35
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.apphibernation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.FileUtils;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+
+@SmallTest
+public class HibernationStateDiskStoreTest {
+ private static final String STATES_FILE_NAME = "states";
+ private final MockScheduledExecutorService mMockScheduledExecutorService =
+ new MockScheduledExecutorService();
+
+ private File mFile;
+ private HibernationStateDiskStore<String> mHibernationStateDiskStore;
+
+
+ @Before
+ public void setUp() {
+ mFile = new File(InstrumentationRegistry.getContext().getCacheDir(), "test");
+ mHibernationStateDiskStore = new HibernationStateDiskStore<>(mFile,
+ new MockProtoReadWriter(), mMockScheduledExecutorService, STATES_FILE_NAME);
+ }
+
+ @After
+ public void tearDown() {
+ FileUtils.deleteContentsAndDir(mFile);
+ }
+
+ @Test
+ public void testScheduleWriteHibernationStates_writesDataThatCanBeRead() {
+ // GIVEN some data to be written
+ List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B"));
+
+ // WHEN the data is written
+ mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite);
+ mMockScheduledExecutorService.executeScheduledTask();
+
+ // THEN the read data is equal to what was written
+ List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates();
+ for (int i = 0; i < toWrite.size(); i++) {
+ assertEquals(toWrite.get(i), storedStrings.get(i));
+ }
+ }
+
+ @Test
+ public void testScheduleWriteHibernationStates_laterWritesOverwritePrevious() {
+ // GIVEN store has some data it is scheduled to write
+ mHibernationStateDiskStore.scheduleWriteHibernationStates(
+ new ArrayList<>(Arrays.asList("C", "D")));
+
+ // WHEN a write is scheduled with new data
+ List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B"));
+ mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite);
+ mMockScheduledExecutorService.executeScheduledTask();
+
+ // THEN the written data is the last scheduled data
+ List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates();
+ for (int i = 0; i < toWrite.size(); i++) {
+ assertEquals(toWrite.get(i), storedStrings.get(i));
+ }
+ }
+
+ /**
+ * Mock proto read / writer that just writes and reads a list of String data.
+ */
+ private final class MockProtoReadWriter implements ProtoReadWriter<List<String>> {
+ private static final long FIELD_ID = 1;
+
+ @Override
+ public void writeToProto(@NonNull ProtoOutputStream stream,
+ @NonNull List<String> data) {
+ for (int i = 0, size = data.size(); i < size; i++) {
+ stream.write(FIELD_ID, data.get(i));
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<String> readFromProto(@NonNull ProtoInputStream stream)
+ throws IOException {
+ ArrayList<String> list = new ArrayList<>();
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ list.add(stream.readString(FIELD_ID));
+ }
+ return list;
+ }
+ }
+
+ /**
+ * Mock scheduled executor service that has minimum implementation and can synchronously
+ * execute scheduled tasks.
+ */
+ private final class MockScheduledExecutorService implements ScheduledExecutorService {
+
+ Runnable mScheduledRunnable = null;
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ mScheduledRunnable = command;
+ return Mockito.mock(ScheduledFuture.class);
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
+ long period, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+ long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
+ TimeUnit unit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ throw new UnsupportedOperationException();
+ }
+
+ void executeScheduledTask() {
+ mScheduledRunnable.run();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 8d744c4..9b6c723 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -700,14 +700,14 @@
public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
SearchSpec searchSpec =
new SearchSpec.Builder()
- .addSchemaType("FakeType")
+ .addFilterSchemas("FakeType")
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build();
mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
searchSpec =
new SearchSpec.Builder()
- .addNamespace("FakeNamespace")
+ .addFilterNamespaces("FakeNamespace")
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build();
mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 95aac60..54da643 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -33,6 +33,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Optional;
+
import javax.annotation.Nullable;
/**
@@ -43,9 +45,9 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public final class DeviceStateManagerServiceTest {
- private static final int DEFAULT_DEVICE_STATE = 0;
- private static final int OTHER_DEVICE_STATE = 1;
- private static final int UNSUPPORTED_DEVICE_STATE = 999;
+ private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
+ private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
+ private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(999, "UNSUPPORTED");
private TestDeviceStatePolicy mPolicy;
private TestDeviceStateProvider mProvider;
@@ -61,47 +63,53 @@
@Test
public void requestStateChange() {
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
- mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ OTHER_DEVICE_STATE.getIdentifier());
}
@Test
public void requestStateChange_pendingState() {
mPolicy.blockConfigure();
- mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ OTHER_DEVICE_STATE.getIdentifier());
- mProvider.notifyRequestState(DEFAULT_DEVICE_STATE);
+ mProvider.notifyRequestState(DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ OTHER_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure();
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void requestStateChange_unsupportedState() {
- mProvider.notifyRequestState(UNSUPPORTED_DEVICE_STATE);
+ mProvider.notifyRequestState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
@@ -113,85 +121,83 @@
@Test
public void requestOverrideState() {
- mService.setOverrideState(OTHER_DEVICE_STATE);
+ mService.setOverrideState(OTHER_DEVICE_STATE.getIdentifier());
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ OTHER_DEVICE_STATE.getIdentifier());
// Committed state is set back to the requested state once the override is cleared.
mService.clearOverrideState();
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void requestOverrideState_unsupportedState() {
- mService.setOverrideState(UNSUPPORTED_DEVICE_STATE);
+ mService.setOverrideState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
// Committed state remains the same as the override state is unsupported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void supportedStatesChanged() {
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
- mProvider.notifySupportedDeviceStates(new int []{ DEFAULT_DEVICE_STATE });
+ mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
// The current committed and requests states do not change because the current state remains
// supported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- }
-
- @Test
- public void supportedStatesChanged_invalidState() {
- assertThrows(IllegalArgumentException.class, () -> {
- mProvider.notifySupportedDeviceStates(new int []{ INVALID_DEVICE_STATE });
- });
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
}
@Test
public void supportedStatesChanged_unsupportedRequestedState() {
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
- mProvider.notifySupportedDeviceStates(new int []{ OTHER_DEVICE_STATE });
+ mProvider.notifySupportedDeviceStates(new DeviceState[]{ OTHER_DEVICE_STATE });
// The current requested state is cleared because it is no longer supported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState(), Optional.empty());
- mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
- assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE);
}
@Test
public void supportedStatesChanged_unsupportedOverrideState() {
- mService.setOverrideState(OTHER_DEVICE_STATE);
+ mService.setOverrideState(OTHER_DEVICE_STATE.getIdentifier());
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ OTHER_DEVICE_STATE.getIdentifier());
- mProvider.notifySupportedDeviceStates(new int []{ DEFAULT_DEVICE_STATE });
+ mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
// Committed state is set back to the requested state as the override state is no longer
// supported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
@@ -199,23 +205,27 @@
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
assertNotNull(callback.getLastNotifiedValue());
- assertEquals(callback.getLastNotifiedValue().intValue(), OTHER_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedValue().intValue(),
+ OTHER_DEVICE_STATE.getIdentifier());
- mProvider.notifyRequestState(DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE);
+ mProvider.notifyRequestState(DEFAULT_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedValue().intValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.blockConfigure();
- mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
// The callback should not have been notified of the state change as the policy is still
// pending callback.
- assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedValue().intValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure();
// Now that the policy is finished processing the callback should be notified of the state
// change.
- assertEquals(callback.getLastNotifiedValue().intValue(), OTHER_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedValue().intValue(),
+ OTHER_DEVICE_STATE.getIdentifier());
}
@Test
@@ -223,7 +233,8 @@
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertNotNull(callback.getLastNotifiedValue());
- assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedValue().intValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
@@ -275,9 +286,8 @@
}
private static final class TestDeviceStateProvider implements DeviceStateProvider {
- private int[] mSupportedDeviceStates = new int[]{ DEFAULT_DEVICE_STATE,
+ private DeviceState[] mSupportedDeviceStates = new DeviceState[]{ DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE };
- private int mCurrentDeviceState = DEFAULT_DEVICE_STATE;
private Listener mListener;
@Override
@@ -288,17 +298,16 @@
mListener = listener;
mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
- mListener.onStateChanged(mCurrentDeviceState);
+ mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
}
- public void notifySupportedDeviceStates(int[] supportedDeviceStates) {
+ public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
mSupportedDeviceStates = supportedDeviceStates;
mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
}
- public void notifyRequestState(int state) {
- mCurrentDeviceState = state;
- mListener.onStateChanged(state);
+ public void notifyRequestState(int identifier) {
+ mListener.onStateChanged(identifier);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index ae966aa..f0b4f1b 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -227,6 +227,31 @@
}
@Test
+ public void testPhysicalStrategyRecalculateSplines() {
+ Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS,
+ BACKLIGHT_RANGE);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
+ float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
+ for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
+ adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
+ }
+
+ // Default is unadjusted
+ assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */);
+ assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */);
+
+ // When adjustment is turned on, adjustment array is used
+ strategy.recalculateSplines(true, adjustedNits50p);
+ assertEquals(1.3425f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */);
+ assertEquals(239.25f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */);
+
+ // When adjustment is turned off, adjustment array is ignored
+ strategy.recalculateSplines(false, adjustedNits50p);
+ assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */);
+ assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */);
+ }
+
+ @Test
public void testDefaultStrategyIsPhysical() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index e02a46af..d362791 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -335,6 +335,9 @@
assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
assertTrue(event.nightMode);
assertEquals(3333, event.colorTemperature);
+ assertTrue(event.reduceBrightColors);
+ assertEquals(40, event.reduceBrightColorsStrength);
+ assertEquals(20f, event.reduceBrightColorsOffset, FLOAT_DELTA);
assertEquals("a.package", event.packageName);
assertEquals(0, event.userId);
assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
@@ -561,6 +564,9 @@
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+ mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+ mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
startTracker(mTracker);
mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
batteryChangeEvent(30, 100));
@@ -592,6 +598,9 @@
assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
assertTrue(event.nightMode);
assertEquals(3339, event.colorTemperature);
+ assertTrue(event.reduceBrightColors);
+ assertEquals(40, event.reduceBrightColorsStrength);
+ assertEquals(20f, event.reduceBrightColorsOffset, FLOAT_DELTA);
assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
assertTrue(event.isUserSetBrightness);
assertFalse(event.isDefaultBrightnessConfig);
@@ -606,6 +615,9 @@
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+ mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+ mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
startTracker(mTracker);
mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
batteryChangeEvent(30, 100));
@@ -639,6 +651,7 @@
assertEquals(brightness, event.brightness, FLOAT_DELTA);
assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
assertTrue(event.nightMode);
+ assertTrue(event.reduceBrightColors);
assertEquals(3339, event.colorTemperature);
}
@@ -661,6 +674,9 @@
builder.setBatteryLevel(0.7f);
builder.setNightMode(false);
builder.setColorTemperature(345);
+ builder.setReduceBrightColors(false);
+ builder.setReduceBrightColorsStrength(40);
+ builder.setReduceBrightColorsOffset(20f);
builder.setLastBrightness(50f);
builder.setColorValues(new long[] {23, 34, 45}, 1000L);
BrightnessChangeEvent event = builder.build();
@@ -684,6 +700,9 @@
assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
assertEquals(event.nightMode, event2.nightMode);
assertEquals(event.colorTemperature, event2.colorTemperature);
+ assertEquals(event.reduceBrightColors, event2.reduceBrightColors);
+ assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength);
+ assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA);
assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets);
assertEquals(event.colorSampleDuration, event2.colorSampleDuration);
@@ -1020,6 +1039,18 @@
}
@Override
+ public int getReduceBrightColorsStrength(Context context) {
+ return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
+ 0);
+ }
+
+ @Override
+ public boolean isReduceBrightColorsActivated(Context context) {
+ return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ 0) == 1;
+ }
+
+ @Override
public DisplayedContentSample sampleColor(int noFramesToSample) {
return new DisplayedContentSample(600L,
null,
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
new file mode 100644
index 0000000..35014dc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.color;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ReduceBrightColorsTintControllerTest {
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ final Resources mockResources = mock(Resources.class);
+ when(mockResources.getStringArray(
+ com.android.internal.R.array.config_reduceBrightColorsCoefficients))
+ .thenReturn(new String[]{"-0.000000000000001", "-0.955555555555554",
+ "1.000000000000000"});
+ when(mockResources.getStringArray(
+ com.android.internal.R.array.config_reduceBrightColorsCoefficientsNonlinear))
+ .thenReturn(new String[]{"-0.4429953456", "-0.2434077725", "0.9809063061"});
+ mContext = mock(Context.class);
+ when(mContext.getResources()).thenReturn(mockResources);
+ }
+
+ @Test
+ public void setAndGetMatrix() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(50);
+ tintController.setActivated(true);
+ assertThat(tintController.getStrength()).isEqualTo(50);
+ assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+ .containsExactly(
+ 0.5222222f, 0f, 0f, 0f,
+ 0f, 0.5222222f, 0f, 0f,
+ 0f, 0f, 0.5222222f, 0f,
+ 0f, 0f, 0f, 1f)
+ .inOrder();
+ }
+
+ @Test
+ public void setAndGetMatrixClampToZero() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(-50);
+ tintController.setActivated(true);
+ assertThat(tintController.getStrength()).isEqualTo(0);
+ assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+ .containsExactly(
+ 1f, 0f, 0f, 0f,
+ 0f, 1f, 0f, 0f,
+ 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f)
+ .inOrder();
+ }
+
+ @Test
+ public void setAndGetMatrixClampTo100() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(120);
+ tintController.setActivated(true);
+ assertThat(tintController.getStrength()).isEqualTo(100);
+ assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+ .containsExactly(
+ 0.04444444f, 0f, 0f, 0f,
+ 0f, 0.04444444f, 0f, 0f,
+ 0f, 0f, 0.04444444f, 0f,
+ 0f, 0f, 0f, 1f)
+ .inOrder();
+ }
+
+ @Test
+ public void returnsIdentityMatrixWhenNotActivated() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(50);
+ tintController.setActivated(true);
+ tintController.setActivated(false);
+ assertThat(tintController.getStrength()).isEqualTo(50);
+ assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+ .containsExactly(
+ 1f, 0f, 0f, 0f,
+ 0f, 1f, 0f, 0f,
+ 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f)
+ .inOrder();
+ }
+
+ @Test
+ public void getAdjustedBrightnessZeroRbcStrengthFullBrightness() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(0);
+ assertThat(tintController.getAdjustedBrightness(450f)).isEqualTo(450f);
+ }
+
+ @Test
+ public void getAdjustedBrightnessFullRbcStrengthFullBrightness() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(100);
+ assertThat(tintController.getAdjustedBrightness(450f)).isEqualTo(19.999998f);
+ }
+
+ @Test
+ public void getAdjustedBrightnessZeroRbcStrengthLowBrightness() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(0);
+ assertThat(tintController.getAdjustedBrightness(2.2f)).isEqualTo(2.2f);
+ }
+
+ @Test
+ public void getAdjustedBrightnessFullRbcStrengthLowBrightness() {
+ final ReduceBrightColorsTintController tintController =
+ new ReduceBrightColorsTintController();
+ tintController.setUp(mContext, /* needsLinear= */ true);
+ tintController.setMatrix(100);
+ assertThat(tintController.getAdjustedBrightness(2.2f)).isEqualTo(0.09777778f);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java
new file mode 100644
index 0000000..275e7c7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.graphics.fonts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.platform.test.annotations.Presubmit;
+
+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 java.io.File;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FontCrashDetectorTest {
+
+ private File mCacheDir;
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest");
+ FileUtils.deleteContentsAndDir(mCacheDir);
+ mCacheDir.mkdirs();
+ }
+
+ @Test
+ public void detectCrash() throws Exception {
+ // Prepare a marker file.
+ File file = new File(mCacheDir, "detectCrash");
+ assertThat(file.createNewFile()).isTrue();
+
+ FontCrashDetector detector = new FontCrashDetector(file);
+ assertThat(detector.hasCrashed()).isTrue();
+
+ detector.clear();
+ assertThat(detector.hasCrashed()).isFalse();
+ assertThat(file.exists()).isFalse();
+ }
+
+ @Test
+ public void monitorCrash() {
+ File file = new File(mCacheDir, "monitorCrash");
+ FontCrashDetector detector = new FontCrashDetector(file);
+ assertThat(detector.hasCrashed()).isFalse();
+
+ FontCrashDetector.MonitoredBlock block = detector.start();
+ assertThat(file.exists()).isTrue();
+
+ block.close();
+ assertThat(file.exists()).isFalse();
+ }
+}
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 c10cee9..86054e4 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
@@ -42,6 +42,8 @@
long expectedModifiedDate = 1234567890;
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
config.lastModifiedDate = expectedModifiedDate;
+ config.updatedFontDirs.add("~~abc");
+ config.updatedFontDirs.add("~~def");
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PersistentSystemFontConfig.writeToXml(baos, config);
@@ -54,6 +56,7 @@
PersistentSystemFontConfig.loadFromXml(bais, another);
assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
+ assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
}
}
}
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 d067790..cb83b0f 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
@@ -23,7 +23,9 @@
import android.content.Context;
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;
@@ -37,11 +39,12 @@
import org.junit.runner.RunWith;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -147,21 +150,23 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dirForPreparation.loadFontFileMap();
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
.isEqualTo(expectedModifiedDate);
- installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+ dirForPreparation.update(Arrays.asList(
+ newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+ newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
- //
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
.isNotEqualTo(expectedModifiedDate);
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -177,6 +182,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
}
@@ -187,10 +193,12 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
- installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+ dirForPreparation.loadFontFileMap();
+ dirForPreparation.update(Arrays.asList(
+ newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+ newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -199,6 +207,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -211,10 +220,12 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
- installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+ dirForPreparation.loadFontFileMap();
+ dirForPreparation.update(Arrays.asList(
+ newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+ newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -224,6 +235,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -236,10 +248,12 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
- installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
- installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+ dirForPreparation.loadFontFileMap();
+ dirForPreparation.update(Arrays.asList(
+ newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+ newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -250,6 +264,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
// For foo.ttf, preinstalled font (revision 5) should be used.
assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
// For bar.ttf, updated font (revision 4) should be used.
@@ -268,18 +283,48 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
new File("/dev/null"));
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
}
@Test
+ public void construct_afterBatchFailure() throws Exception {
+ FakeFontFileParser parser = new FakeFontFileParser();
+ FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+ UpdatableFontDir dirForPreparation = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dirForPreparation.loadFontFileMap();
+ dirForPreparation.update(
+ Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+ try {
+ dirForPreparation.update(Arrays.asList(
+ newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,2", "Invalid signature")));
+ fail("Batch update with invalid signature should fail");
+ } catch (FontManagerService.SystemFontException e) {
+ // Expected
+ }
+
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dir.loadFontFileMap();
+ // 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);
+ }
+
+ @Test
public void installFontFile() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
- installFontFile(dir, "test,1", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1);
File fontFile = dir.getFontFileMap().get("test.ttf");
@@ -295,10 +340,11 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
- installFontFile(dir, "test,1", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
- installFontFile(dir, "test,2", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2);
assertThat(mapBeforeUpgrade).containsKey("test.ttf");
@@ -313,10 +359,11 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
- installFontFile(dir, "test,2", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
try {
- installFontFile(dir, "test,1", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
fail("Expect IllegalArgumentException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
@@ -333,9 +380,28 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
- installFontFile(dir, "foo,1", GOOD_SIGNATURE);
- installFontFile(dir, "bar,2", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+ dir.update(Collections.singletonList(newFontUpdateRequest("bar,2", GOOD_SIGNATURE)));
+ assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
+ assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
+ assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
+ assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(2);
+ }
+
+ @Test
+ public void installFontFile_batch() 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("foo,1", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,2", GOOD_SIGNATURE)));
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -349,9 +415,11 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
try {
- installFontFile(dir, "test,1", "Invalid signature");
+ dir.update(
+ Collections.singletonList(newFontUpdateRequest("test,1", "Invalid signature")));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -368,9 +436,10 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
try {
- installFontFile(dir, "test,1", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
fail("Expect IllegalArgumentException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
@@ -398,9 +467,11 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
readonlyFile);
+ dir.loadFontFileMap();
try {
- installFontFile(dir, "test,2", GOOD_SIGNATURE);
+ dir.update(
+ Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
.isEqualTo(FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG);
@@ -429,9 +500,10 @@
return 0;
}
}, fakeFsverityUtil, mConfigFile);
+ dir.loadFontFileMap();
try {
- installFontFile(dir, "foo,1", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -456,9 +528,10 @@
return 0;
}
}, fakeFsverityUtil, mConfigFile);
+ dir.loadFontFileMap();
try {
- installFontFile(dir, "foo,1", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -491,9 +564,10 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
try {
- installFontFile(dir, "foo,1", GOOD_SIGNATURE);
+ dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -502,13 +576,36 @@
assertThat(dir.getFontFileMap()).isEmpty();
}
- private void installFontFile(UpdatableFontDir dir, String content, String signature)
+ @Test
+ public void installFontFile_batchFailure() throws Exception {
+ FakeFontFileParser parser = new FakeFontFileParser();
+ FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dir.loadFontFileMap();
+
+ dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+ try {
+ dir.update(Arrays.asList(
+ newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
+ newFontUpdateRequest("bar,2", "Invalid signature")));
+ fail("Batch update with invalid signature should fail");
+ } catch (FontManagerService.SystemFontException e) {
+ // Expected
+ }
+ // 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);
+ }
+
+ private FontUpdateRequest newFontUpdateRequest(String content, String signature)
throws Exception {
File file = File.createTempFile("font", "ttf", mCacheDir);
FileUtils.stringToFile(file, content);
- try (FileInputStream in = new FileInputStream(file)) {
- dir.installFontFile(in.getFD(), signature.getBytes());
- }
+ return new FontUpdateRequest(
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY),
+ signature.getBytes());
}
private void writeConfig(PersistentSystemFontConfig.Config config,
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 15a9bcf..263cf48 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -21,9 +21,11 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
-import android.util.Log;
+import android.annotation.NonNull;
import android.util.Pair;
+import android.util.SparseIntArray;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
@@ -58,106 +60,116 @@
* Represents running and pending jobs.
*/
class Jobs {
- public int runningFg;
- public int runningBg;
- public int pendingFg;
- public int pendingBg;
+ public final SparseIntArray running = new SparseIntArray();
+ public final SparseIntArray pending = new SparseIntArray();
public void maybeEnqueueJobs(double startRatio, double fgJobRatio) {
while (mRandom.nextDouble() < startRatio) {
if (mRandom.nextDouble() < fgJobRatio) {
- pendingFg++;
+ pending.put(WORK_TYPE_TOP, pending.get(WORK_TYPE_TOP) + 1);
} else {
- pendingBg++;
+ pending.put(WORK_TYPE_BG, pending.get(WORK_TYPE_BG) + 1);
}
}
}
public void maybeFinishJobs(double stopRatio) {
- for (int i = runningBg; i > 0; i--) {
+ for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
if (mRandom.nextDouble() < stopRatio) {
- runningBg--;
+ running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
}
}
- for (int i = runningFg; i > 0; i--) {
+ for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
if (mRandom.nextDouble() < stopRatio) {
- runningFg--;
+ running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
}
}
}
}
-
- private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
- mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical",
- totalMax,
- // defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg),
- Pair.create(WORK_TYPE_BG, minBg)),
- // defaultMax
- List.of(Pair.create(WORK_TYPE_BG, maxBg))));
+ private void recount(Jobs jobs, int totalMax,
+ @NonNull List<Pair<Integer, Integer>> minLimits,
+ @NonNull List<Pair<Integer, Integer>> maxLimits) {
+ mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig(
+ "test", totalMax, minLimits, maxLimits));
mWorkCountTracker.resetCounts();
- for (int i = 0; i < jobs.runningFg; i++) {
- mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
- }
- for (int i = 0; i < jobs.runningBg; i++) {
- mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
- }
+ for (int i = 0; i < jobs.running.size(); ++i) {
+ final int workType = jobs.running.keyAt(i);
+ final int count = jobs.running.valueAt(i);
- for (int i = 0; i < jobs.pendingFg; i++) {
- mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
+ for (int c = 0; c < count; ++c) {
+ mWorkCountTracker.incrementRunningJobCount(workType);
+ }
}
- for (int i = 0; i < jobs.pendingBg; i++) {
- mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG);
+ for (int i = 0; i < jobs.pending.size(); ++i) {
+ final int workType = jobs.pending.keyAt(i);
+ final int count = jobs.pending.valueAt(i);
+
+ for (int c = 0; c < count; ++c) {
+ mWorkCountTracker.incrementPendingJobCount(workType);
+ }
}
mWorkCountTracker.onCountDone();
+ }
- while ((jobs.pendingFg > 0
+ private void startPendingJobs(Jobs jobs) {
+ while ((jobs.pending.get(WORK_TYPE_TOP) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
- || (jobs.pendingBg > 0
+ || (jobs.pending.get(WORK_TYPE_BG) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
final boolean isStartingFg = mRandom.nextBoolean();
if (isStartingFg) {
- if (jobs.pendingFg > 0
+ if (jobs.pending.get(WORK_TYPE_TOP) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
- jobs.pendingFg--;
- jobs.runningFg++;
+ jobs.pending.put(WORK_TYPE_TOP, jobs.pending.get(WORK_TYPE_TOP) - 1);
+ jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) + 1);
mWorkCountTracker.stageJob(WORK_TYPE_TOP);
mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
}
} else {
- if (jobs.pendingBg > 0
+ if (jobs.pending.get(WORK_TYPE_BG) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
- jobs.pendingBg--;
- jobs.runningBg++;
+ jobs.pending.put(WORK_TYPE_BG, jobs.pending.get(WORK_TYPE_BG) - 1);
+ jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) + 1);
mWorkCountTracker.stageJob(WORK_TYPE_BG);
mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
}
}
}
-
- Log.i(TAG, "" + mWorkCountTracker);
}
/**
* Used by the following testRandom* tests.
*/
- private void checkRandom(Jobs jobs, int numTests, int totalMax, int maxBg, int minBg,
+ private void checkRandom(Jobs jobs, int numTests, int totalMax,
+ @NonNull List<Pair<Integer, Integer>> minLimits,
+ @NonNull List<Pair<Integer, Integer>> maxLimits,
double startRatio, double fgJobRatio, double stopRatio) {
for (int i = 0; i < numTests; i++) {
-
jobs.maybeFinishJobs(stopRatio);
jobs.maybeEnqueueJobs(startRatio, fgJobRatio);
- startPendingJobs(jobs, totalMax, maxBg, minBg);
+ recount(jobs, totalMax, minLimits, maxLimits);
+ startPendingJobs(jobs);
- assertThat(jobs.runningFg).isAtMost(totalMax);
- assertThat(jobs.runningBg).isAtMost(totalMax);
- assertThat(jobs.runningFg + jobs.runningBg).isAtMost(totalMax);
- assertThat(jobs.runningBg).isAtMost(maxBg);
+ int totalRunning = 0;
+ for (int r = 0; r < jobs.running.size(); ++r) {
+ final int numRunning = jobs.running.valueAt(r);
+ assertWithMessage(
+ "Work type " + jobs.running.keyAt(r) + " is running too many jobs")
+ .that(numRunning).isAtMost(totalMax);
+ totalRunning += numRunning;
+ }
+ assertThat(totalRunning).isAtMost(totalMax);
+ for (Pair<Integer, Integer> maxLimit : maxLimits) {
+ assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
+ .that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
+ }
}
}
@@ -170,13 +182,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.1;
final double fgJobRatio = 0.5;
final double startRatio = 0.1;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio , stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -185,13 +198,14 @@
final int numTests = 5000;
final int totalMax = 2;
- final int maxBg = 2;
- final int minBg = 0;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5;
final double fgJobRatio = 0.5;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -200,13 +214,14 @@
final int numTests = 5000;
final int totalMax = 2;
- final int maxBg = 2;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
final double fgJobRatio = 0.5;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -215,13 +230,14 @@
final int numTests = 5000;
final int totalMax = 10;
- final int maxBg = 2;
- final int minBg = 0;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5;
final double fgJobRatio = 0.5;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -230,13 +246,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
final double fgJobRatio = 0.1;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -245,13 +262,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
final double fgJobRatio = 0.9;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -260,13 +278,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.4;
final double fgJobRatio = 0.1;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -275,53 +294,213 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.4;
final double fgJobRatio = 0.9;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
/** Used by the following tests */
- private void checkSimple(int totalMax, int maxBg, int minBg,
- int runningFg, int runningBg, int pendingFg, int pendingBg,
- int resultRunningFg, int resultRunningBg, int resultPendingFg, int resultPendingBg) {
+ private void checkSimple(int totalMax,
+ @NonNull List<Pair<Integer, Integer>> minLimits,
+ @NonNull List<Pair<Integer, Integer>> maxLimits,
+ @NonNull List<Pair<Integer, Integer>> running,
+ @NonNull List<Pair<Integer, Integer>> pending,
+ @NonNull List<Pair<Integer, Integer>> resultRunning,
+ @NonNull List<Pair<Integer, Integer>> resultPending) {
final Jobs jobs = new Jobs();
- jobs.runningFg = runningFg;
- jobs.runningBg = runningBg;
- jobs.pendingFg = pendingFg;
- jobs.pendingBg = pendingBg;
+ for (Pair<Integer, Integer> run : running) {
+ jobs.running.put(run.first, run.second);
+ }
+ for (Pair<Integer, Integer> pend : pending) {
+ jobs.pending.put(pend.first, pend.second);
+ }
- startPendingJobs(jobs, totalMax, maxBg, minBg);
+ recount(jobs, totalMax, minLimits, maxLimits);
+ startPendingJobs(jobs);
-// fail(mWorkerCountTracker.toString());
- assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
- assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
-
- assertThat(jobs.pendingFg).isEqualTo(resultPendingFg);
- assertThat(jobs.pendingBg).isEqualTo(resultPendingBg);
+ for (Pair<Integer, Integer> run : resultRunning) {
+ assertWithMessage("Incorrect running result for work type " + run.first)
+ .that(jobs.running.get(run.first)).isEqualTo(run.second);
+ }
+ for (Pair<Integer, Integer> pend : resultPending) {
+ assertWithMessage("Incorrect pending result for work type " + pend.first)
+ .that(jobs.pending.get(pend.first)).isEqualTo(pend.second);
+ }
}
-
@Test
public void testBasic() {
- // Args are:
- // First 3: Total-max, bg-max, bg-min.
- // Next 2: Running FG / BG
- // Next 2: Pending FG / BG
- // Next 4: Result running FG / BG, pending FG/BG.
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 1, 0, /*res run/pen=*/ 1, 0, 0, 0);
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+ /* resPen */ List.of());
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 0, /*res run/pen=*/ 6, 0, 4, 0);
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 4)));
// When there are BG jobs pending, 2 (min-BG) jobs should run.
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 5), Pair.create(WORK_TYPE_BG, 1)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 5)));
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 1)));
- checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43);
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 43)));
- checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 47)));
+
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 4)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 1)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 48)));
+
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+ /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
+
+ // This could happen if we lower the effective config due to higher memory pressure after
+ // we've already started running jobs. We shouldn't stop already running jobs, but also
+ // shouldn't start new ones.
+ checkSimple(5,
+ /* min */ List.of(),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
+ }
+
+ /** Tests that the counter updates properly when jobs are stopped. */
+ @Test
+ public void testJobLifecycleLoop() {
+ final Jobs jobs = new Jobs();
+ jobs.pending.put(WORK_TYPE_TOP, 11);
+ jobs.pending.put(WORK_TYPE_BG, 10);
+
+ final int totalMax = 6;
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 1));
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+ recount(jobs, totalMax, minLimits, maxLimits);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(6);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+ // Stop all jobs
+ jobs.maybeFinishJobs(1);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(8);
+
+ // Stop only a bg job and make sure the counter only allows another bg job to start.
+ jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(7);
+
+ // Stop only a top job and make sure the counter only allows another top job to start.
+ jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_NONE);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(7);
+
+ // Now that there are no more TOP jobs pending, BG should be able to start when TOP stops.
+ for (int i = jobs.running.get(WORK_TYPE_TOP); i > 0; --i) {
+ jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+ }
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(5);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(3);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index fba36cb..c28292f 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -18,6 +18,9 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.provider.DeviceConfig;
import android.util.Pair;
@@ -32,6 +35,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -58,42 +62,90 @@
}
private void check(@Nullable DeviceConfig.Properties config,
- int defaultTotal, int defaultMaxBg, int defaultMinBg,
- int expectedTotal, int expectedMaxBg, int expectedMinBg) throws Exception {
+ int defaultTotal,
+ @Nullable Pair<Integer, Integer> defaultTopLimits,
+ @Nullable Pair<Integer, Integer> defaultBgLimits,
+ boolean expectedValid, int expectedTotal,
+ @NonNull Pair<Integer, Integer> expectedTopLimits,
+ @NonNull Pair<Integer, Integer> expectedBgLimits) throws Exception {
resetConfig();
if (config != null) {
DeviceConfig.setProperties(config);
}
- final WorkTypeConfig counts = new WorkTypeConfig("test",
- defaultTotal,
- // defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, defaultTotal - defaultMaxBg),
- Pair.create(WORK_TYPE_BG, defaultMinBg)),
- // defaultMax
- List.of(Pair.create(WORK_TYPE_BG, defaultMaxBg)));
+ List<Pair<Integer, Integer>> defaultMin = new ArrayList<>();
+ List<Pair<Integer, Integer>> defaultMax = new ArrayList<>();
+ Integer val;
+ if (defaultTopLimits != null) {
+ if ((val = defaultTopLimits.first) != null) {
+ defaultMin.add(Pair.create(WORK_TYPE_TOP, val));
+ }
+ if ((val = defaultTopLimits.second) != null) {
+ defaultMax.add(Pair.create(WORK_TYPE_TOP, val));
+ }
+ }
+ if (defaultBgLimits != null) {
+ if ((val = defaultBgLimits.first) != null) {
+ defaultMin.add(Pair.create(WORK_TYPE_BG, val));
+ }
+ if ((val = defaultBgLimits.second) != null) {
+ defaultMax.add(Pair.create(WORK_TYPE_BG, val));
+ }
+ }
+
+ final WorkTypeConfig counts;
+ try {
+ counts = new WorkTypeConfig("test",
+ defaultTotal, defaultMin, defaultMax);
+ if (!expectedValid) {
+ fail("Invalid config successfully created");
+ return;
+ }
+ } catch (IllegalArgumentException e) {
+ if (expectedValid) {
+ throw e;
+ } else {
+ // Success
+ return;
+ }
+ }
counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
Assert.assertEquals(expectedTotal, counts.getMaxTotal());
- Assert.assertEquals(expectedMaxBg, counts.getMax(WORK_TYPE_BG));
- Assert.assertEquals(expectedMinBg, counts.getMinReserved(WORK_TYPE_BG));
+ Assert.assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP));
+ Assert.assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP));
+ Assert.assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG));
+ Assert.assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG));
}
@Test
public void test() throws Exception {
// Tests with various combinations.
- check(null, /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
- check(null, /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
- check(null, /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
- check(null, /*default*/ -1, -1, -1, /*expected*/ 1, 1, 0);
- check(null, /*default*/ 5, 5, 5, /*expected*/ 5, 5, 4);
- check(null, /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
- check(null, /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
- check(null, /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
- check(null, /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
- check(null, /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
- check(null, /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
+ check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1),
+ /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1));
+ check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0),
+ /*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1));
+ check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0),
+ /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+ check(null, /*default*/ -1, null, Pair.create(-1, -1),
+ /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+ check(null, /*default*/ 5, null, Pair.create(5, 5),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
+ check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5),
+ /*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5));
+ check(null, /*default*/ 4, null, Pair.create(6, 5),
+ /*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4));
+ check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1),
+ /*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1));
+ check(null, /*default*/ 15, null, Pair.create(15, 15),
+ /*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15));
+ check(null, /*default*/ 16, null, Pair.create(16, 16),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
+ check(null, /*default*/ 20, null, Pair.create(20, 20),
+ /*expected*/ false, 16, Pair.create(1, 16), Pair.create(15, 16));
+ check(null, /*default*/ 20, null, Pair.create(16, 16),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
// Test for overriding with a setting string.
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
@@ -101,15 +153,26 @@
.setInt(KEY_MAX_BG, 4)
.setInt(KEY_MIN_BG, 3)
.build(),
- /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_TOTAL, 5).build(),
- /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_BG, 4).build(),
- /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MIN_BG, 3).build(),
- /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9));
+ check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+ .setInt(KEY_MAX_TOTAL, 20)
+ .setInt(KEY_MAX_BG, 20)
+ .setInt(KEY_MIN_BG, 8)
+ .build(),
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
index 90edaef..709b009 100644
--- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
@@ -37,9 +37,10 @@
private KeySetManagerService mKsms;
public PackageSetting generateFakePackageSetting(String name) {
- return new PackageSetting(name, name, new File(mContext.getCacheDir(), "fakeCodePath"),
- "", "", "", "", 1, 0, 0, 0 /*sharedUserId*/, null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/);
+ return new PackageSettingBuilder()
+ .setName(name)
+ .setCodePath(new File(mContext.getCacheDir(), "fakeCodePath").getAbsolutePath())
+ .build();
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index 4ce1bbc..558fb30 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -107,10 +107,15 @@
// Create a real (non-null) PackageSetting and confirm that the removed
// users are copied properly
- setting = new PackageSetting("name", "realName", new File("codePath"),
- "legacyNativeLibraryPathString", "primaryCpuAbiString", "secondaryCpuAbiString",
- "cpuAbiOverrideString", 0, 0, 0, 0,
- null, null, null);
+ setting = new PackageSettingBuilder()
+ .setName("name")
+ .setRealName("realName")
+ .setCodePath("codePath")
+ .setLegacyNativeLibraryPathString("legacyNativeLibraryPathString")
+ .setPrimaryCpuAbiString("primaryCpuAbiString")
+ .setSecondaryCpuAbiString("secondaryCpuAbiString")
+ .setCpuAbiOverrideString("cpuAbiOverrideString")
+ .build();
pri.populateUsers(new int[] {
1, 2, 3, 4, 5
}, setting);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 333ec929..59458e8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -33,10 +33,10 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -59,6 +59,7 @@
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.LocalServices;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
@@ -80,6 +81,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -94,10 +96,14 @@
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
@Mock
LegacyPermissionDataProvider mPermissionDataProvider;
+ @Mock
+ DomainVerificationManagerInternal mDomainVerificationManager;
@Before
public void initializeMocks() {
MockitoAnnotations.initMocks(this);
+ when(mDomainVerificationManager.generateNewId())
+ .thenAnswer(invocation -> UUID.randomUUID());
}
@Before
@@ -112,10 +118,7 @@
throws ReflectiveOperationException, IllegalAccessException {
/* write out files and read */
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
verifyKeySetMetaData(settings);
}
@@ -126,10 +129,7 @@
throws ReflectiveOperationException, IllegalAccessException {
// write out files and read
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
// write out, read back in and verify the same
@@ -142,10 +142,7 @@
public void testSettingsReadOld() {
// Write delegateshellthe package files and make sure they're parsed properly the first time
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -164,16 +161,12 @@
public void testNewPackageRestrictionsFile() throws ReflectiveOperationException {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
settings.writeLPr();
// Create Settings again to make it read from the new files
- settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -200,10 +193,7 @@
@Test
public void testReadPackageRestrictions_noSuspendingPackage() {
writePackageRestrictions_noSuspendingPackageXml(0);
- final Object lock = new Object();
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- lock);
+ Settings settingsUnderTest = makeSettings();
final WatchableTester watcher =
new WatchableTester(settingsUnderTest, "noSuspendingPackage");
watcher.register();
@@ -244,10 +234,7 @@
@Test
public void testReadPackageRestrictions_noSuspendParamsMap() {
writePackageRestrictions_noSuspendParamsMapXml(0);
- final Object lock = new Object();
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- lock);
+ final Settings settingsUnderTest = makeSettings();
final WatchableTester watcher =
new WatchableTester(settingsUnderTest, "noSuspendParamsMap");
watcher.register();
@@ -281,9 +268,7 @@
@Test
public void testReadWritePackageRestrictions_suspendInfo() {
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- new Object());
+ final Settings settingsUnderTest = makeSettings();
final WatchableTester watcher = new WatchableTester(settingsUnderTest, "suspendInfo");
watcher.register();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
@@ -397,9 +382,7 @@
@Test
public void testReadWritePackageRestrictions_distractionFlags() {
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- new Object());
+ final Settings settingsUnderTest = makeSettings();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
@@ -440,10 +423,7 @@
@Test
public void testWriteReadUsesStaticLibraries() {
- final Context context = InstrumentationRegistry.getTargetContext();
- final Object lock = new Object();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ final Settings settingsUnderTest = makeSettings();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
ps1.appId = Process.FIRST_APPLICATION_UID;
ps1.pkg = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
@@ -516,10 +496,7 @@
public void testEnableDisable() {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable");
watcher.register();
assertThat(settings.readLPw(createFakeUsers()), is(true));
@@ -585,7 +562,8 @@
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
}
@@ -606,7 +584,8 @@
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
final PackageSetting testPkgSetting01 = new PackageSetting(
PACKAGE_NAME /*pkgName*/,
REAL_PACKAGE_NAME /*realPkgName*/,
@@ -621,7 +600,8 @@
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
testPkgSetting01.copyFrom(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
}
@@ -648,7 +628,8 @@
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
assertThat(testPkgSetting01.pkgFlags, is(0));
@@ -681,7 +662,8 @@
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
@@ -698,12 +680,9 @@
/** Update package; changing shared user throws exception */
@Test
public void testUpdatePackageSetting03() {
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- final Settings testSettings01 = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
- testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+ settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 =
createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
try {
@@ -720,7 +699,8 @@
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
fail("Expected a PackageManagerException");
} catch (PackageManagerException expected) {
}
@@ -752,7 +732,8 @@
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
@@ -790,7 +771,8 @@
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.appId, is(0));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -808,12 +790,9 @@
/** Create PackageSetting for a shared user */
@Test
public void testCreateNewSetting03() {
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- final Settings testSettings01 = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
- testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+ settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 = Settings.createNewSetting(
PACKAGE_NAME,
null /*originalPkg*/,
@@ -834,7 +813,8 @@
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.appId, is(10064));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -875,7 +855,8 @@
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.appId, is(10064));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -934,6 +915,7 @@
assertThat(origPkgSetting.getPathString(), is(testPkgSetting.getPathString()));
assertSame(origPkgSetting.cpuAbiOverrideString, testPkgSetting.cpuAbiOverrideString);
assertThat(origPkgSetting.cpuAbiOverrideString, is(testPkgSetting.cpuAbiOverrideString));
+ assertThat(origPkgSetting.getDomainSetId(), is(testPkgSetting.getDomainSetId()));
assertThat(origPkgSetting.firstInstallTime, is(testPkgSetting.firstInstallTime));
assertSame(origPkgSetting.installSource, testPkgSetting.installSource);
assertThat(origPkgSetting.installPermissionsFixed,
@@ -976,8 +958,6 @@
assertNotSame(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
// No equals() method for SparseArray object
// assertThat(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
- assertSame(origPkgSetting.verificationInfo, testPkgSetting.verificationInfo);
- assertThat(origPkgSetting.verificationInfo, is(testPkgSetting.verificationInfo));
assertThat(origPkgSetting.versionCode, is(testPkgSetting.versionCode));
assertSame(origPkgSetting.volumeUuid, testPkgSetting.volumeUuid);
assertThat(origPkgSetting.volumeUuid, is(testPkgSetting.volumeUuid));
@@ -1006,7 +986,8 @@
sharedUserId,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
}
private PackageSetting createPackageSetting(String packageName) {
@@ -1024,7 +1005,8 @@
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
}
private @NonNull List<UserInfo> createFakeUsers() {
@@ -1212,6 +1194,12 @@
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
+ private Settings makeSettings() {
+ return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
+ mRuntimePermissionsPersistence, mPermissionDataProvider,
+ mDomainVerificationManager, new Object());
+ }
+
private void verifyKeySetMetaData(Settings settings)
throws ReflectiveOperationException, IllegalAccessException {
ArrayMap<String, PackageSetting> packages =
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 90c2982..e6a238a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -546,12 +546,17 @@
}
private static PackageSetting mockPkgSetting(AndroidPackage pkg) {
- return new PackageSetting(pkg.getPackageName(), pkg.getRealPackage(),
- new File(pkg.getPath()), null, pkg.getPrimaryCpuAbi(), pkg.getSecondaryCpuAbi(),
- null, pkg.getVersionCode(),
- PackageInfoUtils.appInfoFlags(pkg, null),
- PackageInfoUtils.appInfoPrivateFlags(pkg, null),
- pkg.getSharedUserLabel(), null, null, null);
+ return new PackageSettingBuilder()
+ .setName(pkg.getPackageName())
+ .setRealName(pkg.getRealPackage())
+ .setCodePath(pkg.getPath())
+ .setPrimaryCpuAbiString(pkg.getPrimaryCpuAbi())
+ .setSecondaryCpuAbiString(pkg.getSecondaryCpuAbi())
+ .setPVersionCode(pkg.getLongVersionCode())
+ .setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
+ .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
+ .setSharedUserId(pkg.getSharedUserLabel())
+ .build();
}
// NOTE: The equality assertions below are based on code autogenerated by IntelliJ.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 84551c5..f75751b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -22,10 +22,10 @@
import android.util.SparseArray;
import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
import java.io.File;
import java.util.Map;
+import java.util.UUID;
public class PackageSettingBuilder {
private String mName;
@@ -48,6 +48,7 @@
private long[] mUsesStaticLibrariesVersions;
private Map<String, ArraySet<String>> mMimeGroups;
private PackageParser.SigningDetails mSigningDetails;
+ private UUID mDomainSetId = UUID.randomUUID();
public PackageSettingBuilder setPackage(AndroidPackage pkg) {
this.mPkg = pkg;
@@ -163,12 +164,17 @@
return this;
}
+ public PackageSettingBuilder setDomainSetId(UUID domainSetId) {
+ mDomainSetId = domainSetId;
+ return this;
+ }
+
public PackageSetting build() {
final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
mPrivateFlags, mSharedUserId, mUsesStaticLibraries, mUsesStaticLibrariesVersions,
- mMimeGroups);
+ mMimeGroups, mDomainSetId);
packageSetting.signatures = mSigningDetails != null
? new PackageSignatures(mSigningDetails)
: new PackageSignatures();
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
index 90658055..27f3eec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
@@ -465,10 +465,11 @@
private static PackageSetting createPackageSetting() {
// Generic PackageSetting object with values from a test app installed on a device to be
// used to test the methods under the PackageSignatures signatures data member.
- File appPath = new File("/data/app/app");
- PackageSetting result = new PackageSetting("test.app", null, appPath,
- "/data/app/app", null, null, null, 1, 940097092, 0, 0 /*userId*/, null, null,
- null /*mimeGroups*/);
- return result;
+ return new PackageSettingBuilder()
+ .setName("test.app")
+ .setCodePath("/data/app/app")
+ .setPVersionCode(1)
+ .setPkgFlags(940097092)
+ .build();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 1cfbad9..938e4cc 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -53,18 +53,10 @@
assertThat(testUserState.equals(oldUserState), is(true));
oldUserState = new PackageUserState();
- oldUserState.appLinkGeneration = 6;
- assertThat(testUserState.equals(oldUserState), is(false));
-
- oldUserState = new PackageUserState();
oldUserState.ceDataInode = 4000L;
assertThat(testUserState.equals(oldUserState), is(false));
oldUserState = new PackageUserState();
- oldUserState.domainVerificationStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
- assertThat(testUserState.equals(oldUserState), is(false));
-
- oldUserState = new PackageUserState();
oldUserState.enabled = COMPONENT_ENABLED_STATE_ENABLED;
assertThat(testUserState.equals(oldUserState), is(false));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index d8c3979..b5add84 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -50,6 +50,7 @@
import android.util.Pair;
import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.PackageImpl;
@@ -91,6 +92,14 @@
when(mMockInjector.getAbiHelper()).thenReturn(mMockPackageAbiHelper);
when(mMockInjector.getUserManagerInternal()).thenReturn(mMockUserManager);
when(mMockInjector.getCompatibility()).thenReturn(mMockCompatibility);
+
+ DomainVerificationManagerInternal domainVerificationManager =
+ mock(DomainVerificationManagerInternal.class);
+ when(domainVerificationManager.generateNewId())
+ .thenAnswer(invocation -> UUID.randomUUID());
+
+ when(mMockInjector.getDomainVerificationManagerInternal())
+ .thenReturn(domainVerificationManager);
}
@Before
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index bb223b3..fb13d87 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -55,6 +55,7 @@
import java.io.File;
import java.io.InputStream;
+import java.util.Collections;
import java.util.function.Function;
/**
@@ -523,7 +524,7 @@
int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
- flags, false /*collectCertificates*/);
+ flags, Collections.emptyList(), false /*collectCertificates*/);
if (result.isError()) {
throw new IllegalStateException(result.getErrorMessage(), result.getException());
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
index d8910de..4dc9a90 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
@@ -19,12 +19,10 @@
import android.annotation.RawRes
import android.content.Context
import android.content.pm.parsing.ParsingPackage
-import android.content.pm.parsing.ParsingPackageImpl
import android.content.pm.parsing.ParsingPackageUtils
import android.content.pm.parsing.result.ParseInput
import android.content.pm.parsing.result.ParseInput.DeferredError
import android.content.pm.parsing.result.ParseResult
-import android.content.res.TypedArray
import android.os.Build
import androidx.test.InstrumentationRegistry
import com.android.frameworks.servicestests.R
@@ -130,7 +128,7 @@
input.copyTo(output)
}
}
- return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/,
+ return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(),
false /*collectCertificates*/)
}
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 0bea584..4f36c8a 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -40,6 +40,7 @@
import androidx.annotation.NonNull;
import com.android.server.LocalServices;
+import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import org.junit.After;
@@ -61,7 +62,8 @@
* Run with <code>atest DeviceStateProviderImplTest</code>.
*/
public final class DeviceStateProviderImplTest {
- private final ArgumentCaptor<int[]> mIntArrayCaptor = ArgumentCaptor.forClass(int[].class);
+ private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
+ DeviceState[].class);
private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
private Context mContext;
@@ -120,11 +122,12 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
- assertArrayEquals(new int[] { DEFAULT_DEVICE_STATE }, mIntArrayCaptor.getValue());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(new DeviceState[]{DEFAULT_DEVICE_STATE},
+ mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture());
- assertEquals(DEFAULT_DEVICE_STATE, mIntegerCaptor.getValue().intValue());
+ assertEquals(DEFAULT_DEVICE_STATE.getIdentifier(), mIntegerCaptor.getValue().intValue());
}
@Test
@@ -146,8 +149,10 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
- assertArrayEquals(new int[] { 1, 2 }, mIntArrayCaptor.getValue());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, null),
+ new DeviceState(2, null) };
+ assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
@@ -166,6 +171,7 @@
+ " </device-state>\n"
+ " <device-state>\n"
+ " <identifier>2</identifier>\n"
+ + " <name>CLOSED</name>\n"
+ " <conditions>\n"
+ " <lid-switch>\n"
+ " <open>false</open>\n"
@@ -180,8 +186,10 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
- assertArrayEquals(new int[] { 1, 2 }, mIntArrayCaptor.getValue());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, null),
+ new DeviceState(2, "CLOSED") };
+ assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(2, mIntegerCaptor.getValue().intValue());
@@ -189,8 +197,7 @@
Mockito.clearInvocations(listener);
provider.notifyLidSwitchChanged(0, true /* lidOpen */);
-
- verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
@@ -203,6 +210,7 @@
String configString = "<device-state-config>\n"
+ " <device-state>\n"
+ " <identifier>1</identifier>\n"
+ + " <name>CLOSED</name>\n"
+ " <conditions>\n"
+ " <sensor>\n"
+ " <type>" + sensor.getStringType() + "</type>\n"
@@ -215,6 +223,7 @@
+ " </device-state>\n"
+ " <device-state>\n"
+ " <identifier>2</identifier>\n"
+ + " <name>HALF_OPENED</name>\n"
+ " <conditions>\n"
+ " <sensor>\n"
+ " <type>" + sensor.getStringType() + "</type>\n"
@@ -228,6 +237,7 @@
+ " </device-state>\n"
+ " <device-state>\n"
+ " <identifier>3</identifier>\n"
+ + " <name>OPENED</name>\n"
+ " <conditions>\n"
+ " <sensor>\n"
+ " <type>" + sensor.getStringType() + "</type>\n"
@@ -246,8 +256,10 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
- assertArrayEquals(new int[] { 1, 2, 3 }, mIntArrayCaptor.getValue());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(
+ new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"),
+ new DeviceState(3, "OPENED") }, mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
@@ -256,11 +268,11 @@
SensorEvent event0 = mock(SensorEvent.class);
event0.sensor = sensor;
- FieldSetter.setField(event0, event0.getClass().getField("values"), new float[] { 180 });
+ FieldSetter.setField(event0, event0.getClass().getField("values"), new float[]{180});
provider.onSensorChanged(event0);
- verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(3, mIntegerCaptor.getValue().intValue());
@@ -268,11 +280,11 @@
SensorEvent event1 = mock(SensorEvent.class);
event1.sensor = sensor;
- FieldSetter.setField(event1, event1.getClass().getField("values"), new float[] { 90 });
+ FieldSetter.setField(event1, event1.getClass().getField("values"), new float[]{90});
provider.onSensorChanged(event1);
- verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(2, mIntegerCaptor.getValue().intValue());
@@ -280,11 +292,11 @@
SensorEvent event2 = mock(SensorEvent.class);
event2.sensor = sensor;
- FieldSetter.setField(event2, event2.getClass().getField("values"), new float[] { 0 });
+ FieldSetter.setField(event2, event2.getClass().getField("values"), new float[]{0});
provider.onSensorChanged(event2);
- verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 1d0b595..592eea1 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -179,7 +179,8 @@
.thenReturn(mPowerSaveState);
when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
- when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true);
+ when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
+ .thenReturn(true);
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 84b690f..53ddb28 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -221,7 +221,7 @@
}
@Override
- public EnergyMeasurement[] readEnergyMeters(int[] channelIds) {
+ public EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
EnergyMeasurement[] energyMeasurementList = new EnergyMeasurement[ENERGY_METER_COUNT];
for (int i = 0; i < energyMeasurementList.length; i++) {
energyMeasurementList[i] = new EnergyMeasurement();
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index c59ead9..6dcfb42 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -1123,10 +1123,10 @@
}
/**
- * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
- * like the real thing should, it also asserts preconditions.
+ * A fake implementation of {@link TimeDetectorStrategyImpl.Environment}. Besides tracking
+ * changes and behaving like the real thing should, it also asserts preconditions.
*/
- private static class FakeCallback implements TimeDetectorStrategyImpl.Callback {
+ private static class FakeEnvironment implements TimeDetectorStrategyImpl.Environment {
private boolean mAutoTimeDetectionEnabled;
private boolean mWakeLockAcquired;
private long mElapsedRealtimeMillis;
@@ -1254,41 +1254,41 @@
*/
private class Script {
- private final FakeCallback mFakeCallback;
+ private final FakeEnvironment mFakeEnvironment;
private final TimeDetectorStrategyImpl mTimeDetectorStrategy;
Script() {
- mFakeCallback = new FakeCallback();
- mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeCallback);
+ mFakeEnvironment = new FakeEnvironment();
+ mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment);
}
Script pokeAutoTimeDetectionEnabled(boolean enabled) {
- mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
+ mFakeEnvironment.pokeAutoTimeDetectionEnabled(enabled);
return this;
}
Script pokeFakeClocks(TimestampedValue<Instant> timeInfo) {
- mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
- mFakeCallback.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
+ mFakeEnvironment.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
+ mFakeEnvironment.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
return this;
}
Script pokeThresholds(int systemClockUpdateThreshold) {
- mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
+ mFakeEnvironment.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
return this;
}
Script pokeAutoOriginPriorities(@Origin int... autoOriginPriorities) {
- mFakeCallback.pokeAutoOriginPriorities(autoOriginPriorities);
+ mFakeEnvironment.pokeAutoOriginPriorities(autoOriginPriorities);
return this;
}
long peekElapsedRealtimeMillis() {
- return mFakeCallback.peekElapsedRealtimeMillis();
+ return mFakeEnvironment.peekElapsedRealtimeMillis();
}
long peekSystemClockMillis() {
- return mFakeCallback.peekSystemClockMillis();
+ return mFakeEnvironment.peekSystemClockMillis();
}
Script simulateTelephonyTimeSuggestion(TelephonyTimeSuggestion timeSuggestion) {
@@ -1324,13 +1324,13 @@
}
Script simulateAutoTimeDetectionToggle() {
- mFakeCallback.simulateAutoTimeZoneDetectionToggle();
+ mFakeEnvironment.simulateAutoTimeZoneDetectionToggle();
mTimeDetectorStrategy.handleAutoTimeConfigChanged();
return this;
}
Script simulateTimePassing(long clockIncrementMillis) {
- mFakeCallback.simulateTimePassing(clockIncrementMillis);
+ mFakeEnvironment.simulateTimePassing(clockIncrementMillis);
return this;
}
@@ -1342,14 +1342,14 @@
}
Script verifySystemClockWasNotSetAndResetCallTracking() {
- mFakeCallback.verifySystemClockNotSet();
- mFakeCallback.resetCallTracking();
+ mFakeEnvironment.verifySystemClockNotSet();
+ mFakeEnvironment.resetCallTracking();
return this;
}
Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
- mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
- mFakeCallback.resetCallTracking();
+ mFakeEnvironment.verifySystemClockWasSet(expectedSystemClockMillis);
+ mFakeEnvironment.resetCallTracking();
return this;
}
@@ -1427,7 +1427,7 @@
ManualTimeSuggestion generateManualTimeSuggestion(Instant suggestedTime) {
TimestampedValue<Long> utcTime =
new TimestampedValue<>(
- mFakeCallback.peekElapsedRealtimeMillis(),
+ mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
return new ManualTimeSuggestion(utcTime);
}
@@ -1461,7 +1461,7 @@
NetworkTimeSuggestion generateNetworkTimeSuggestion(Instant suggestedTime) {
TimestampedValue<Long> utcTime =
new TimestampedValue<>(
- mFakeCallback.peekElapsedRealtimeMillis(),
+ mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
return new NetworkTimeSuggestion(utcTime);
}
@@ -1473,7 +1473,7 @@
GnssTimeSuggestion generateGnssTimeSuggestion(Instant suggestedTime) {
TimestampedValue<Long> utcTime =
new TimestampedValue<>(
- mFakeCallback.peekElapsedRealtimeMillis(),
+ mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
return new GnssTimeSuggestion(utcTime);
}
@@ -1485,7 +1485,7 @@
ExternalTimeSuggestion generateExternalTimeSuggestion(Instant suggestedTime) {
TimestampedValue<Long> utcTime =
new TimestampedValue<>(
- mFakeCallback.peekElapsedRealtimeMillis(),
+ mFakeEnvironment.peekElapsedRealtimeMillis(),
suggestedTime.toEpochMilli());
return new ExternalTimeSuggestion(utcTime);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index a6ffd20..c8dba5f 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -167,15 +167,15 @@
createConfig(null, false /* geoDetection */);
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
- private FakeCallback mFakeCallback;
+ private FakeEnvironment mFakeEnvironment;
private MockConfigChangeListener mMockConfigChangeListener;
@Before
public void setUp() {
- mFakeCallback = new FakeCallback();
+ mFakeEnvironment = new FakeEnvironment();
mMockConfigChangeListener = new MockConfigChangeListener();
- mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeCallback);
+ mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
mTimeZoneDetectorStrategy.addConfigChangeListener(mMockConfigChangeListener);
}
@@ -183,7 +183,7 @@
public void testGetCurrentUserConfiguration() {
new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
ConfigurationInternal expectedConfiguration =
- mFakeCallback.getConfigurationInternal(USER_ID);
+ mFakeEnvironment.getConfigurationInternal(USER_ID);
assertEquals(expectedConfiguration,
mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal());
}
@@ -490,7 +490,7 @@
private void makeSlotIndex1SuggestionAndCheckState(Script script, TelephonyTestCase testCase) {
// Give the next suggestion a different zone from the currently set device time zone;
- String currentZoneId = mFakeCallback.getDeviceTimeZone();
+ String currentZoneId = mFakeEnvironment.getDeviceTimeZone();
String suggestionZoneId =
"Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London";
TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion =
@@ -610,9 +610,9 @@
}
/**
- * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
- * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
- * current settings.
+ * The {@link TimeZoneDetectorStrategyImpl.Environment} is left to detect whether changing the
+ * time zone is actually necessary. This test proves that the strategy doesn't assume it knows
+ * the current settings.
*/
@Test
public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
@@ -958,7 +958,7 @@
return builder.build();
}
- static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
+ static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
private final TestState<ConfigurationInternal> mConfigurationInternal = new TestState<>();
private final TestState<String> mTimeZoneId = new TestState<>();
@@ -1051,12 +1051,12 @@
private class Script {
Script initializeConfig(ConfigurationInternal configuration) {
- mFakeCallback.initializeConfig(configuration);
+ mFakeEnvironment.initializeConfig(configuration);
return this;
}
Script initializeTimeZoneSetting(String zoneId) {
- mFakeCallback.initializeTimeZoneSetting(zoneId);
+ mFakeEnvironment.initializeTimeZoneSetting(zoneId);
return this;
}
@@ -1084,7 +1084,7 @@
Script simulateManualTimeZoneSuggestion(
@UserIdInt int userId, ManualTimeZoneSuggestion manualTimeZoneSuggestion,
boolean expectedResult) {
- mFakeCallback.assertKnownUser(userId);
+ mFakeEnvironment.assertKnownUser(userId);
boolean actualResult = mTimeZoneDetectorStrategy.suggestManualTimeZone(
userId, manualTimeZoneSuggestion);
assertEquals(expectedResult, actualResult);
@@ -1104,26 +1104,26 @@
* state was last reset.
*/
Script verifyTimeZoneNotChanged() {
- mFakeCallback.assertTimeZoneNotChanged();
+ mFakeEnvironment.assertTimeZoneNotChanged();
return this;
}
/** Verifies the device's time zone has been set and clears change tracking history. */
Script verifyTimeZoneChangedAndReset(String zoneId) {
- mFakeCallback.assertTimeZoneChangedTo(zoneId);
- mFakeCallback.commitAllChanges();
+ mFakeEnvironment.assertTimeZoneChangedTo(zoneId);
+ mFakeEnvironment.commitAllChanges();
return this;
}
Script verifyTimeZoneChangedAndReset(ManualTimeZoneSuggestion suggestion) {
- mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
- mFakeCallback.commitAllChanges();
+ mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
+ mFakeEnvironment.commitAllChanges();
return this;
}
Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
- mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
- mFakeCallback.commitAllChanges();
+ mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
+ mFakeEnvironment.commitAllChanges();
return this;
}
@@ -1131,9 +1131,9 @@
* Verifies that the configuration has been changed to the expected value.
*/
Script verifyConfigurationChangedAndReset(ConfigurationInternal expected) {
- mFakeCallback.mConfigurationInternal.assertHasBeenSet();
- assertEquals(expected, mFakeCallback.mConfigurationInternal.getLatest());
- mFakeCallback.commitAllChanges();
+ mFakeEnvironment.mConfigurationInternal.assertHasBeenSet();
+ assertEquals(expected, mFakeEnvironment.mConfigurationInternal.getLatest());
+ mFakeEnvironment.commitAllChanges();
// Also confirm the listener triggered.
mMockConfigChangeListener.verifyOnChangeCalled();
@@ -1146,7 +1146,7 @@
* {@link TimeZoneConfiguration} have been changed.
*/
Script verifyConfigurationNotChanged() {
- mFakeCallback.mConfigurationInternal.assertHasNotBeenSet();
+ mFakeEnvironment.mConfigurationInternal.assertHasNotBeenSet();
// Also confirm the listener did not trigger.
mMockConfigChangeListener.verifyOnChangeNotCalled();
@@ -1154,7 +1154,7 @@
}
Script resetConfigurationTracking() {
- mFakeCallback.commitAllChanges();
+ mFakeEnvironment.commitAllChanges();
return this;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 28d313b..e71c2f5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -294,6 +294,8 @@
private InputDevice createInputDevice(int id, boolean hasVibrator) {
return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
- null, hasVibrator, false, false, false /* hasSensor */);
+ null, hasVibrator, false, false, false /* hasSensor */, false /* hasBattery */);
+
+
}
}
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index 4c82818..c6e35cf 100644
--- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -26,21 +26,17 @@
object MockitoUtils {
val ANSWER_THROWS = Answer<Any?> {
when (val name = it.method.name) {
- "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+ "toString" -> return@Answer try {
+ Answers.CALLS_REAL_METHODS.answer(it)
+ } catch (e: Exception) {
+ "failure calling toString"
+ }
else -> {
val arguments = it.arguments
?.takeUnless { it.isEmpty() }
- ?.mapIndexed { index, arg ->
- try {
- arg?.toString()
- } catch (e: Exception) {
- "toString[$index] threw ${e.message}"
- }
- }
- ?.joinToString()
- ?.let {
- "with $it"
- }
+ ?.mapIndexed { index, arg -> arg.attemptToString(index) }
+ ?.joinToString { it.attemptToString(null) }
+ ?.let { "with $it" }
.orEmpty()
throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " +
@@ -48,6 +44,19 @@
}
}
}
+
+ // Sometimes mocks won't have a toString method, so try-catch and return some default
+ private fun Any?.attemptToString(id: Any? = null): String {
+ return try {
+ toString()
+ } catch (e: Exception) {
+ if (id == null) {
+ e.message ?: "ERROR"
+ } else {
+ "$id ${e.message}"
+ }
+ }
+ }
}
inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
@@ -83,4 +92,4 @@
inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit = {}) =
spyThrowOnUnmocked<T>(null, block)
-inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
\ No newline at end of file
+inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8c744c9..fc1cb70 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -3536,6 +3536,28 @@
assertFalse(conversationWrapperContainsChannel(convos, channel2));
}
+ @Test
+ public void testGetConversations_parentDeleted() {
+ String convoId = "convo";
+ NotificationChannel messages =
+ new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
+
+ NotificationChannel channel =
+ new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
+ channel.setConversationId(messages.getId(), convoId);
+ channel.setImportantConversation(true);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+ mHelper.permanentlyDeleteNotificationChannel(PKG_O, UID_O, "messages");
+
+ List<ConversationChannelWrapper> convos =
+ mHelper.getConversations(IntArray.wrap(new int[] {0}), true);
+
+ assertEquals(1, convos.size());
+ assertTrue(conversationWrapperContainsChannel(convos, channel));
+ }
+
private boolean conversationWrapperContainsChannel(List<ConversationChannelWrapper> list,
NotificationChannel expected) {
for (ConversationChannelWrapper ccw : list) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index de2cc76..42b080e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1715,9 +1715,8 @@
doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
- any() /* requestedVisibility */, any() /* outFrame */,
- any() /* outInputChannel */, any() /* outInsetsState */,
- any() /* outActiveControls */);
+ any() /* requestedVisibility */, any() /* outInputChannel */,
+ any() /* outInsetsState */, any() /* outActiveControls */);
mAtm.mWindowManager.mStartingSurfaceController
.createTaskSnapshotSurface(activity, snapshot);
} catch (RemoteException ignored) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 5c67db7..89b962b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
@@ -50,6 +50,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
@@ -408,6 +409,32 @@
}
@Test
+ public void testRestrictAppBoundsToOverrideBounds() {
+ final RootDisplayArea root =
+ new DisplayAreaPolicyBuilderTest.SurfacelessDisplayAreaRoot(mWm);
+ final DisplayArea<DisplayArea> da = new DisplayArea<>(mWm, ANY, "Test_DA");
+ root.addChild(da, POSITION_TOP);
+ final Rect displayBounds = new Rect(0, 0, 1800, 2800);
+ final Rect displayAppBounds = new Rect(0, 100, 1800, 2800);
+ final Rect daBounds = new Rect(0, 1400, 1800, 2800);
+ root.setBounds(displayBounds);
+
+ // DA inherit parent app bounds.
+ final Configuration displayConfig = new Configuration();
+ displayConfig.windowConfiguration.setAppBounds(displayAppBounds);
+ root.onRequestedOverrideConfigurationChanged(displayConfig);
+
+ assertEquals(displayAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+
+ // Restrict DA appBounds to override Bounds
+ da.setBounds(daBounds);
+
+ final Rect expectedDaAppBounds = new Rect(daBounds);
+ expectedDaAppBounds.intersect(displayAppBounds);
+ assertEquals(expectedDaAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+ }
+
+ @Test
public void testGetOrientation() {
final DisplayArea.Tokens area = new DisplayArea.Tokens(mWm, ABOVE_TASKS, "test");
final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
@@ -514,7 +541,7 @@
return new WindowState(mWm, mock(Session.class), new TestIWindow(), token,
null /* parentWindow */, 0 /* appOp */, new WindowManager.LayoutParams(),
View.VISIBLE, 0 /* ownerId */, 0 /* showUserId */,
- false /* ownerCanAddInternalSystemWindow */, false /* ownerCanUseBackgroundBlur */);
+ false /* ownerCanAddInternalSystemWindow */);
}
private WindowToken createWindowToken(int type) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 82ffa765..b1ea4a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -55,7 +55,6 @@
import static org.mockito.Mockito.spy;
import static org.testng.Assert.expectThrows;
-import android.app.WindowConfiguration;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -66,6 +65,7 @@
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.InsetsState;
+import android.view.RoundedCorners;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
@@ -99,6 +99,7 @@
private int mRotation = ROTATION_0;
private boolean mHasDisplayCutout;
private boolean mIsLongEdgeDisplayCutout;
+ private boolean mHasRoundedCorners;
private final Rect mDisplayBounds = new Rect();
@@ -123,6 +124,15 @@
updateDisplayFrames();
}
+ void addWindowWithRawInsetsState(WindowState win) {
+ addWindow(win);
+ // Without mPerformLayout in display content, the window cannot see any insets. Override the
+ // insets state with the global one.
+ final InsetsState insetsState =
+ win.getDisplayContent().getInsetsStateController().getRawInsetsState();
+ win.mAboveInsetsState = insetsState;
+ }
+
public void setRotation(int rotation, boolean includingWindows) {
mRotation = rotation;
updateDisplayFrames();
@@ -144,6 +154,11 @@
updateDisplayFrames();
}
+ public void addRoundedCorners() {
+ mHasRoundedCorners = true;
+ updateDisplayFrames();
+ }
+
private void updateDisplayFrames() {
mFrames = createDisplayFrames(
mDisplayContent.getInsetsStateController().getRawInsetsState());
@@ -157,9 +172,11 @@
private DisplayFrames createDisplayFrames(InsetsState insetsState) {
final Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation,
mHasDisplayCutout, mIsLongEdgeDisplayCutout);
+ final RoundedCorners roundedCorners = mHasRoundedCorners
+ ? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
+ : RoundedCorners.NO_ROUNDED_CORNERS;
return new DisplayFrames(mDisplayContent.getDisplayId(),
- insetsState,
- info.first, info.second);
+ insetsState, info.first, info.second, roundedCorners);
}
@Test
@@ -272,7 +289,7 @@
@Test
public void layoutWindowLw_fitStatusBars() {
mWindow.mAttrs.setFitInsetsTypes(Type.statusBars());
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -283,7 +300,7 @@
@Test
public void layoutWindowLw_fitNavigationBars() {
mWindow.mAttrs.setFitInsetsTypes(Type.navigationBars());
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -294,7 +311,7 @@
@Test
public void layoutWindowLw_fitAllSides() {
mWindow.mAttrs.setFitInsetsSides(Side.all());
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -305,7 +322,7 @@
@Test
public void layoutWindowLw_fitTopOnly() {
mWindow.mAttrs.setFitInsetsSides(Side.TOP);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -315,11 +332,12 @@
@Test
public void layoutWindowLw_fitInsetsIgnoringVisibility() {
- final InsetsState state = mWindow.getInsetsState();
+ final InsetsState state =
+ mDisplayContent.getInsetsStateController().getRawInsetsState();
state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
mWindow.mAttrs.setFitInsetsIgnoringVisibility(true);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -329,11 +347,12 @@
@Test
public void layoutWindowLw_fitInsetsNotIgnoringVisibility() {
- final InsetsState state = mWindow.getInsetsState();
+ final InsetsState state =
+ mDisplayContent.getInsetsStateController().getRawInsetsState();
state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
mWindow.mAttrs.setFitInsetsIgnoringVisibility(false);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -349,8 +368,7 @@
state.getSource(InsetsState.ITYPE_IME).setFrame(
0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
- mWindow.mBehindIme = true;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -364,7 +382,7 @@
mWindow.mAttrs.setFitInsetsTypes(Type.displayCutout());
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -379,7 +397,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -395,7 +413,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -411,7 +429,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -427,7 +445,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -442,7 +460,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -457,11 +475,12 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+ mDisplayContent.getInsetsStateController().getRawInsetsState()
+ .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
final InsetsState requestedState = new InsetsState();
requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
mWindow.updateRequestedVisibility(requestedState);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -476,12 +495,13 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+ mDisplayContent.getInsetsStateController().getRawInsetsState()
+ .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
final InsetsState requestedState = new InsetsState();
requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
mWindow.updateRequestedVisibility(requestedState);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -497,7 +517,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -513,7 +533,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -529,7 +549,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -545,7 +565,7 @@
mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY;
mWindow.mAttrs.width = DISPLAY_WIDTH;
mWindow.mAttrs.height = DISPLAY_HEIGHT;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -562,7 +582,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -576,7 +596,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -592,7 +612,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -608,7 +628,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -624,7 +644,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -638,7 +658,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
final int forwardedInsetBottom = 50;
mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom));
@@ -652,77 +672,13 @@
public void layoutHint_appWindow() {
mWindow.mAttrs.setFitInsetsTypes(0);
- final Rect outFrame = new Rect();
final DisplayCutout.ParcelableWrapper outDisplayCutout =
new DisplayCutout.ParcelableWrapper();
final InsetsState outState = new InsetsState();
- mDisplayPolicy.getLayoutHint(mWindow.mAttrs, null /* windowToken */, outFrame,
- outState, true /* localClient */);
-
- assertThat(outFrame, is(outState.getDisplayFrame()));
- assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
- assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(),
- is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT)));
- assertThat(outState.getSource(ITYPE_NAVIGATION_BAR).getFrame(),
- is(new Rect(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT)));
- }
-
- @Test
- public void layoutHint_appWindowInTask() {
- mWindow.mAttrs.setFitInsetsTypes(0);
-
- final Rect taskBounds = new Rect(100, 100, 200, 200);
- final Task task = mWindow.getTask();
- // Force the bounds because the task may resolve different bounds from Task#setBounds.
- task.getWindowConfiguration().setBounds(taskBounds);
-
- final Rect outFrame = new Rect();
- final DisplayCutout.ParcelableWrapper outDisplayCutout =
- new DisplayCutout.ParcelableWrapper();
- final InsetsState outState = new InsetsState();
-
- mDisplayPolicy.getLayoutHint(mWindow.mAttrs, mWindow.mToken, outFrame, outState,
+ mDisplayPolicy.getLayoutHint(mWindow.mAttrs, null /* windowToken */, outState,
true /* localClient */);
- assertThat(outFrame, is(taskBounds));
- assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
- assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(),
- is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT)));
- assertThat(outState.getSource(ITYPE_NAVIGATION_BAR).getFrame(),
- is(new Rect(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT)));
- }
-
- @Test
- public void layoutHint_appWindowInTask_outsideContentFrame() {
- mWindow.mAttrs.setFitInsetsTypes(0);
-
- final InsetsState state =
- mDisplayContent.getInsetsStateController().getRawInsetsState();
- final Rect contentFrame = new Rect(state.getDisplayFrame());
- contentFrame.inset(state.calculateInsets(contentFrame, Type.systemBars(),
- false /* ignoreVisibility */));
-
- // Task is in the nav bar area (usually does not happen, but this is similar enough to
- // the possible overlap with the IME)
- final Rect taskBounds = new Rect(100, contentFrame.bottom + 1,
- 200, contentFrame.bottom + 10);
-
- final Task task = mWindow.getTask();
- // Make the task floating.
- task.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
- // Force the bounds because the task may resolve different bounds from Task#setBounds.
- task.getWindowConfiguration().setBounds(taskBounds);
-
- final Rect outFrame = new Rect();
- final DisplayCutout.ParcelableWrapper outDisplayCutout =
- new DisplayCutout.ParcelableWrapper();
- final InsetsState outState = new InsetsState();
-
- mDisplayPolicy.getLayoutHint(mWindow.mAttrs, mWindow.mToken, outFrame, outState,
- true /* localClient */);
-
- assertThat(outFrame, is(taskBounds));
assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(),
is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT)));
@@ -741,6 +697,8 @@
assertSimulateLayoutSameDisplayFrames();
addDisplayCutout();
assertSimulateLayoutSameDisplayFrames();
+ addRoundedCorners();
+ assertSimulateLayoutSameDisplayFrames();
}
private void assertSimulateLayoutSameDisplayFrames() {
@@ -776,9 +734,13 @@
public void testFixedRotationInsetsSourceFrame() {
doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent)
.rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
- final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+ mWindow.mAboveInsetsState.addSource(mDisplayContent.getInsetsStateController()
+ .getRawInsetsState().peekSource(ITYPE_STATUS_BAR));
+ final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
+ .getSource(ITYPE_STATUS_BAR).getFrame();
mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
- final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+ final Rect rotatedFrame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
+ .getSource(ITYPE_STATUS_BAR).getFrame();
assertEquals(DISPLAY_WIDTH, frame.width());
assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 77537a9..2163661 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -300,8 +300,9 @@
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+ mImeWindow.mAboveInsetsState = state;
mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
- state, displayInfo, null /* displayCutout */);
+ state, displayInfo, null /* displayCutout */, null /* roundedCorners*/);
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index e1aca55..2321a73 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -121,6 +121,8 @@
sMockWm = mock(WindowManagerService.class);
sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
sMockWm.mPolicy = mock(WindowManagerPolicy.class);
+ sMockWm.mConstants = mock(WindowManagerConstants.class);
+ sMockWm.mConstants.mRawSensorLoggingEnabled = true;
}
@AfterClass
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index e6f24da..f91c9d0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index e0fd379..bf3ed69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -45,6 +45,7 @@
import android.app.StatusBarManager;
import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -272,7 +273,6 @@
final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar");
navBar.setHasSurface(true);
navBar.getControllableInsetProvider().setServerVisible(true);
-
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
@@ -337,11 +337,14 @@
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
- addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
- .getControllableInsetProvider().getSource().setVisible(false);
- addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().getSource().setVisible(false);
-
+ final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
+ .getControllableInsetProvider().getSource();
+ final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
+ .getControllableInsetProvider().getSource();
+ statusBarSource.setVisible(false);
+ navBarSource.setVisible(false);
+ mAppWindow.mAboveInsetsState.addSource(navBarSource);
+ mAppWindow.mAboveInsetsState.addSource(statusBarSource);
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 2766438..ee293fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -59,25 +59,6 @@
public class InsetsStateControllerTest extends WindowTestsBase {
@Test
- public void testStripForDispatch_notOwn() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR));
- assertNotNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
- }
-
- @Test
- public void testStripForDispatch_own() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
- .setWindow(statusBar, null, null);
- statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR));
- final InsetsState state = getController().getInsetsForWindow(statusBar);
- assertNull(state.peekSource(ITYPE_STATUS_BAR));
- }
-
- @Test
public void testStripForDispatch_navBar() {
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
@@ -105,6 +86,7 @@
assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR));
+ assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_IME));
}
@Test
@@ -142,14 +124,15 @@
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
- app1.mBehindIme = true;
-
final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
- app2.mBehindIme = false;
+
+ app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME));
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME).isVisible());
- assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME)
+ .isVisible());
+ assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME)
+ .isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -158,7 +141,8 @@
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- app.mBehindIme = true;
+ app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
+ app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
@@ -170,10 +154,10 @@
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- app.mBehindIme = false;
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+ .isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -210,7 +194,8 @@
// app won't get visible IME insets while above IME even when IME is visible.
assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
- assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+ .isVisible());
// Reset invocation counter.
clearInvocations(app);
@@ -219,6 +204,8 @@
app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
mDisplayContent.computeImeTarget(true);
mDisplayContent.applySurfaceChangesTransaction();
+ app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
+ app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
// Make sure app got notified.
verify(app, atLeast(1)).notifyInsetsChanged();
@@ -234,6 +221,8 @@
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+ app.mAboveInsetsState.set(getController().getRawInsetsState());
+ child.mAboveInsetsState.set(getController().getRawInsetsState());
child.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
mDisplayContent.computeImeTarget(true);
@@ -242,7 +231,8 @@
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
- assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+ .isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -252,6 +242,7 @@
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+ app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME));
child.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -261,7 +252,8 @@
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
- assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+ .isVisible());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index a045100..fa3e3ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
@@ -51,11 +52,13 @@
SurfaceControl.Transaction mTransaction;
private boolean mAreCornersRounded = false;
+ private int mColor = Color.BLACK;
@Before
public void setUp() throws Exception {
mSurfaces = new SurfaceControlMocker();
- mLetterbox = new Letterbox(mSurfaces, StubTransaction::new, () -> mAreCornersRounded);
+ mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
+ () -> mAreCornersRounded, () -> Color.valueOf(mColor));
mTransaction = spy(StubTransaction.class);
}
@@ -171,6 +174,18 @@
}
@Test
+ public void testApplySurfaceChanges_setColor() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ mLetterbox.applySurfaceChanges(mTransaction);
+
+ verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 0, 0});
+
+ mColor = Color.GREEN;
+
+ assertTrue(mLetterbox.needsApplySurfaceChanges());
+ }
+
+ @Test
public void testApplySurfaceChanges_cornersNotRounded_surfaceBehindNotCreated() {
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
mLetterbox.applySurfaceChanges(mTransaction);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 409bad4..c6be987 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -493,7 +493,7 @@
initializeRecentsAnimationController(mController, homeActivity);
// Verify RecentsAnimationController will animate visible leaf task by default.
- verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), eq(null));
+ verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), any());
assertTrue(leafTask.isAnimatingByRecents());
// Make sure isAnimatingByRecents will also return true when it called by the parent task.
@@ -543,6 +543,35 @@
verify(navBarFadeAnimationController, never()).fadeWindowToken(anyBoolean());
}
+ @Test
+ public void testCleanupAnimation_expectExitAnimationDone() {
+ mWm.setRecentsAnimationController(mController);
+ final ActivityRecord homeActivity = createHomeActivity();
+ final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
+ final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
+ activity.addWindow(win1);
+
+ initializeRecentsAnimationController(mController, homeActivity);
+ mController.startAnimation();
+
+ spyOn(win1);
+ spyOn(win1.mWinAnimator);
+ // Simulate when the window is exiting and cleanupAnimation invoked
+ // (e.g. screen off during RecentsAnimation animating), will expect the window receives
+ // onExitAnimationDone to destroy the surface when the removal is allowed.
+ win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+ win1.mHasSurface = true;
+ win1.mAnimatingExit = true;
+ win1.mRemoveOnExit = true;
+ win1.mWindowRemovalAllowed = true;
+ mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ verify(win1).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), any());
+ verify(win1).onExitAnimationDone();
+ verify(win1).destroySurface(eq(false), eq(false));
+ assertFalse(win1.mAnimatingExit);
+ assertFalse(win1.mHasSurface);
+ }
+
private ActivityRecord createHomeActivity() {
final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
.setParentTask(mRootHomeTask)
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6dfbbc7..6f775cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -26,6 +26,7 @@
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -38,6 +39,8 @@
import static com.android.server.wm.Task.ActivityState.STOPPED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -51,14 +54,12 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.TaskStackListener;
import android.app.WindowConfiguration;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
-import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
@@ -71,8 +72,6 @@
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
-
/**
* Tests for Size Compatibility mode.
*
@@ -120,13 +119,13 @@
@Test
public void testKeepBoundsWhenChangingFromFreeformToFullscreen() {
removeGlobalMinSizeRestriction();
- // create freeform display and a freeform app
+ // Create landscape freeform display and a freeform app.
DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000)
.setCanRotate(false)
.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build();
setUpApp(display);
- // Put app window into freeform and then make it a compat app.
+ // Put app window into portrait freeform and then make it a compat app.
final Rect bounds = new Rect(100, 100, 400, 600);
mTask.setBounds(bounds);
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
@@ -139,7 +138,7 @@
final int density = mActivity.getConfiguration().densityDpi;
- // change display configuration to fullscreen
+ // Change display configuration to fullscreen.
Configuration c = new Configuration(display.getRequestedOverrideConfiguration());
c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
display.onRequestedOverrideConfigurationChanged(c);
@@ -149,6 +148,8 @@
assertEquals(bounds.width(), mActivity.getBounds().width());
assertEquals(bounds.height(), mActivity.getBounds().height());
assertEquals(density, mActivity.getConfiguration().densityDpi);
+ // Size compat mode is sandboxed at the activity level.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
}
@Test
@@ -174,6 +175,12 @@
assertEquals(appBounds.height(), appBounds.width() * aspectRatio, 0.5f /* delta */);
// The decor height should be a part of the effective bounds.
assertEquals(mActivity.getBounds().height(), appBounds.height() + notchHeight);
+ // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio.
+ assertActivityMaxBoundsSandboxedForLetterbox();
+ // Activity max bounds ignore notch, since an app can be shown past the notch (although app
+ // is currently limited by the notch).
+ assertThat(mActivity.getWindowConfiguration().getMaxBounds().height())
+ .isEqualTo(displayBounds.height());
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertFitted();
@@ -183,9 +190,17 @@
assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */);
// The notch is no longer on top.
assertEquals(appBounds, mActivity.getBounds());
+ // Activity max bounds are sandboxed.
+ assertActivityMaxBoundsSandboxedForLetterbox();
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
+ // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio.
+ assertActivityMaxBoundsSandboxedForLetterbox();
+ // Activity max bounds ignore notch, since an app can be shown past the notch (although app
+ // is currently limited by the notch).
+ assertThat(mActivity.getWindowConfiguration().getMaxBounds().height())
+ .isEqualTo(displayBounds.height());
}
@Test
@@ -213,6 +228,9 @@
assertEquals(originalBounds.width(), mActivity.getBounds().width());
assertEquals(originalBounds.height(), mActivity.getBounds().height());
assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
+ // Activity is sandboxed; it is in size compat mode since it is not resizable and has a
+ // max aspect ratio.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
assertScaled();
}
@@ -220,11 +238,13 @@
public void testFixedScreenBoundsWhenDisplaySizeChanged() {
setUpDisplaySizeWithApp(1000, 2500);
prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ final DisplayContent display = mActivity.mDisplayContent;
assertFitted();
+ // Activity and task inherit bounds from TaskDisplayArea, since not sandboxed.
+ assertMaxBoundsInheritDisplayAreaBounds();
final Rect origBounds = new Rect(mActivity.getBounds());
final Rect currentBounds = mActivity.getWindowConfiguration().getBounds();
- final DisplayContent display = mActivity.mDisplayContent;
// Change the size of current display.
resizeDisplay(display, 1000, 2000);
@@ -241,6 +261,8 @@
// The position of configuration bounds should be the same as compat bounds.
assertEquals(mActivity.getBounds().left, currentBounds.left);
assertEquals(mActivity.getBounds().top, currentBounds.top);
+ // Activity is sandboxed to the offset size compat bounds.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
// Change display size to a different orientation
resizeDisplay(display, 2000, 1000);
@@ -249,6 +271,8 @@
assertEquals(origBounds.height(), currentBounds.height());
assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation);
assertEquals(Configuration.ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation);
+ // Activity is sandboxed to the offset size compat bounds.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
// The previous resize operation doesn't consider the rotation change after size changed.
// These setups apply the requested orientation to rotation as real case that the top fixed
@@ -268,6 +292,8 @@
assertEquals(origBounds.height(), currentBounds.height());
assertEquals(offsetX, currentBounds.left);
assertScaled();
+ // Activity is sandboxed due to size compat mode.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
}
@Test
@@ -283,6 +309,8 @@
assertEquals(bounds.width(), bounds.height() * maxAspect, 0.0001f /* delta */);
// The position should be horizontal centered.
assertEquals((displayWidth - bounds.width()) / 2, bounds.left);
+ // Activity max bounds should be sandboxed since it is letterboxed.
+ assertActivityMaxBoundsSandboxedForLetterbox();
mActivity.mDisplayContent.setImeLayeringTarget(addWindowToActivity(mActivity));
// Make sure IME cannot attach to the app, otherwise IME window will also be shifted.
@@ -294,6 +322,8 @@
// It should keep non-attachable because the resolved bounds will be computed according to
// the aspect ratio that won't match its parent bounds.
assertFalse(mActivity.mDisplayContent.isImeAttachedToApp());
+ // Activity max bounds should be sandboxed since it is letterboxed.
+ assertActivityMaxBoundsSandboxedForLetterbox();
}
@Test
@@ -319,14 +349,13 @@
}
@Test
- public void testMoveToDifferentOrientDisplay() {
+ public void testMoveToDifferentOrientationDisplay() {
setUpDisplaySizeWithApp(1000, 2500);
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
- final Rect configBounds = mActivity.getWindowConfiguration().getBounds();
- final int origWidth = configBounds.width();
- final int origHeight = configBounds.height();
+ final Rect currentBounds = mActivity.getWindowConfiguration().getBounds();
+ final Rect originalBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
final int notchHeight = 100;
final DisplayContent newDisplay = new TestDisplayContent.Builder(mAtm, 2000, 1000)
@@ -335,37 +364,44 @@
// Move the non-resizable activity to the new display.
mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
// The configuration bounds [820, 0 - 1820, 2500] should keep the same.
- assertEquals(origWidth, configBounds.width());
- assertEquals(origHeight, configBounds.height());
+ assertEquals(originalBounds.width(), currentBounds.width());
+ assertEquals(originalBounds.height(), currentBounds.height());
assertScaled();
+ // Activity max bounds are sandboxed due to size compat mode on the new display.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
final Rect newDisplayBounds = newDisplay.getWindowConfiguration().getBounds();
// The scaled bounds should exclude notch area (1000 - 100 == 360 * 2500 / 1000 = 900).
assertEquals(newDisplayBounds.height() - notchHeight,
- (int) ((float) mActivity.getBounds().width() * origHeight / origWidth));
+ (int) ((float) mActivity.getBounds().width() * originalBounds.height()
+ / originalBounds.width()));
// Recompute the natural configuration in the new display.
mActivity.clearSizeCompatMode();
mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
// Because the display cannot rotate, the portrait activity will fit the short side of
// display with keeping portrait bounds [200, 0 - 700, 1000] in center.
- assertEquals(newDisplayBounds.height(), configBounds.height());
- assertEquals(configBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(),
- configBounds.width());
+ assertEquals(newDisplayBounds.height(), currentBounds.height());
+ assertEquals(currentBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(),
+ currentBounds.width());
assertFitted();
// The appBounds should be [200, 100 - 700, 1000].
final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
- assertEquals(configBounds.width(), appBounds.width());
- assertEquals(configBounds.height() - notchHeight, appBounds.height());
+ assertEquals(currentBounds.width(), appBounds.width());
+ assertEquals(currentBounds.height() - notchHeight, appBounds.height());
+ // Task max bounds are sandboxed due to letterboxing from orientation mismatch with display.
+ assertTaskMaxBoundsSandboxed();
}
@Test
- public void testFixedOrientRotateCutoutDisplay() {
+ public void testFixedOrientationRotateCutoutDisplay() {
// Create a display with a notch/cutout
final int notchHeight = 60;
- setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2500)
+ final int width = 1000;
+ setUpApp(new TestDisplayContent.Builder(mAtm, width, 2500)
.setNotch(notchHeight).build());
- // Bounds=[0, 0 - 1000, 1460], AppBounds=[0, 60 - 1000, 1460].
+ // Bounds=[0, 0 - 1000, 1400], AppBounds=[0, 60 - 1000, 1460].
+ final float maxAspect = 1.4f;
prepareUnresizable(mActivity, 1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
final Rect currentBounds = mActivity.getWindowConfiguration().getBounds();
@@ -373,6 +409,11 @@
final Rect origBounds = new Rect(currentBounds);
final Rect origAppBounds = new Rect(appBounds);
+ // Activity is sandboxed, and bounds include the area consumed by the notch.
+ assertActivityMaxBoundsSandboxedForLetterbox();
+ assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds().height())
+ .isEqualTo(Math.round(width * maxAspect) + notchHeight);
+
// Although the activity is fixed orientation, force rotate the display.
rotateDisplay(mActivity.mDisplayContent, ROTATION_270);
assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation());
@@ -388,10 +429,13 @@
// The position in configuration should be global coordinates.
assertEquals(mActivity.getBounds().left, currentBounds.left);
assertEquals(mActivity.getBounds().top, currentBounds.top);
+
+ // Activity max bounds are sandboxed due to size compat mode.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
}
@Test
- public void testFixedAspOrientChangeOrient() {
+ public void testFixedAspectRatioOrientationChangeOrientation() {
setUpDisplaySizeWithApp(1000, 2500);
final float maxAspect = 1.4f;
@@ -403,6 +447,8 @@
final Rect originalAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
assertEquals((int) (originalBounds.width() * maxAspect), originalBounds.height());
+ // Activity is sandboxed due to fixed aspect ratio.
+ assertActivityMaxBoundsSandboxedForLetterbox();
// Change the fixed orientation.
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
@@ -414,6 +460,8 @@
mActivity.getWindowConfiguration().getAppBounds().height());
assertEquals(originalAppBounds.height(),
mActivity.getWindowConfiguration().getAppBounds().width());
+ // Activity is sandboxed due to fixed aspect ratio.
+ assertActivityMaxBoundsSandboxedForLetterbox();
}
@Test
@@ -462,6 +510,8 @@
// restarted and the override configuration won't be cleared.
verify(mActivity, never()).restartProcessIfVisible();
assertScaled();
+ // Activity max bounds are sandboxed due to size compat mode, even if is not visible.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
// Change display density
display.mBaseDisplayDensity = (int) (0.7f * display.mBaseDisplayDensity);
@@ -478,52 +528,6 @@
}
/**
- * Ensures that {@link TaskStackListener} can receive callback about the activity in size
- * compatibility mode.
- *
- * TODO(b/178327644) Remove after update DC#handleActivitySizeCompatModeIfNeeded
- */
- @Test
- public void testHandleActivitySizeCompatMode() {
- setUpDisplaySizeWithApp(1000, 2000);
- doReturn(true).when(mTask).isOrganized();
- ActivityRecord activity = mActivity;
- activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
- prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
- assertFitted();
-
- final ArrayList<IBinder> compatTokens = new ArrayList<>();
- mAtm.getTaskChangeNotificationController().registerTaskStackListener(
- new TaskStackListener() {
- @Override
- public void onSizeCompatModeActivityChanged(int displayId,
- IBinder activityToken) {
- compatTokens.add(activityToken);
- }
- });
-
- // Resize the display so that the activity exercises size-compat mode.
- resizeDisplay(mTask.mDisplayContent, 1000, 2500);
-
- // Expect the exact token when the activity is in size compatibility mode.
- assertEquals(1, compatTokens.size());
- assertEquals(activity.appToken, compatTokens.get(0));
-
- compatTokens.clear();
- // Make the activity resizable again by restarting it
- activity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
- activity.mVisibleRequested = true;
- activity.restartProcessIfVisible();
- // The full lifecycle isn't hooked up so manually set state to resumed
- activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
- mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
-
- // Expect null token when switching to non-size-compat mode activity.
- assertEquals(1, compatTokens.size());
- assertEquals(null, compatTokens.get(0));
- }
-
- /**
* Ensures that {@link TaskOrganizerController} can receive callback about the activity in size
* compatibility mode.
*/
@@ -582,12 +586,16 @@
// in multi-window mode.
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
assertFalse(activity.shouldUseSizeCompatMode());
+ // Activity and task should not be sandboxed.
+ assertMaxBoundsInheritDisplayAreaBounds();
// The non-resizable activity should not be size compat because the display support
// changing windowing mode from fullscreen to freeform.
mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
assertFalse(activity.shouldUseSizeCompatMode());
+ // Activity and task should not be sandboxed.
+ assertMaxBoundsInheritDisplayAreaBounds();
}
@Test
@@ -645,12 +653,14 @@
assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
mActivity.getLetterboxInsets());
- final BarController statusBarController =
- mActivity.mDisplayContent.getDisplayPolicy().getStatusBarController();
+ final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
// The activity doesn't fill the display, so the letterbox of the rotated activity is
// overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
// be transparent.
- assertFalse(statusBarController.isFullyTransparentAllowed(w));
+ assertFalse(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
+
+ // Activity is sandboxed.
+ assertActivityMaxBoundsSandboxedForLetterbox();
// Make the activity fill the display.
prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
@@ -660,7 +670,8 @@
// The letterbox should only cover the notch area, so status bar can be transparent.
assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
- assertTrue(statusBarController.isFullyTransparentAllowed(w));
+ assertTrue(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
+ assertActivityMaxBoundsSandboxedForLetterbox();
}
@Test
@@ -685,6 +696,8 @@
assertTrue(mTask.isTaskLetterboxed());
assertFalse(mActivity.inSizeCompatMode());
assertEquals(taskBounds, activityBounds);
+ // Activity inherits max bounds from task, since sandboxing applied to task.
+ assertTaskMaxBoundsSandboxed();
// Task bounds should be 700x1400 with the ratio as the display.
assertEquals(displayBounds.height(), taskBounds.height());
@@ -715,6 +728,8 @@
assertScaled();
assertEquals(activityBounds.width(), newActivityBounds.width());
assertEquals(activityBounds.height(), newActivityBounds.height());
+ // Activity max bounds are sandboxed due to size compat mode.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
}
@Test
@@ -726,29 +741,30 @@
// Portrait fixed app without max aspect.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
- Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
- Rect activityBounds = new Rect(mActivity.getBounds());
-
// App should launch in fullscreen.
assertFalse(mTask.isTaskLetterboxed());
assertFalse(mActivity.inSizeCompatMode());
- assertEquals(displayBounds, activityBounds);
+ // Activity and task inherit max bounds from TaskDisplayArea.
+ assertMaxBoundsInheritDisplayAreaBounds();
// Rotate display to landscape.
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
- displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
- activityBounds = new Rect(mActivity.getBounds());
- assertTrue(displayBounds.width() > displayBounds.height());
+ final Rect rotatedDisplayBounds = new Rect(mActivity.mDisplayContent.getBounds());
+ final Rect rotatedActivityBounds = new Rect(mActivity.getBounds());
+ assertTrue(rotatedDisplayBounds.width() > rotatedDisplayBounds.height());
// App should be in size compat.
assertFalse(mTask.isTaskLetterboxed());
assertScaled();
+ assertThat(mActivity.inSizeCompatMode()).isTrue();
+ // Activity max bounds are sandboxed due to size compat mode.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
// App bounds should be 700x1400 with the ratio as the display.
- assertEquals(displayBounds.height(), activityBounds.height());
- assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
- activityBounds.width());
+ assertEquals(rotatedDisplayBounds.height(), rotatedActivityBounds.height());
+ assertEquals(rotatedDisplayBounds.height() * rotatedDisplayBounds.height()
+ / rotatedDisplayBounds.width(), rotatedActivityBounds.width());
}
@Test
@@ -781,14 +797,17 @@
final Rect displayBounds = new Rect(display.getBounds());
final Rect taskBounds = new Rect(mTask.getBounds());
final Rect newActivityBounds = new Rect(newActivity.getBounds());
+ final float displayAspectRatio = (float) displayBounds.height() / displayBounds.width();
// Task and app bounds should be 700x1400 with the ratio as the display.
assertTrue(mTask.isTaskLetterboxed());
assertFalse(newActivity.inSizeCompatMode());
assertEquals(taskBounds, newActivityBounds);
assertEquals(displayBounds.height(), taskBounds.height());
- assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
- taskBounds.width());
+ assertThat(taskBounds.width())
+ .isEqualTo(Math.round(displayBounds.height() * displayAspectRatio));
+ // Task max bounds are sandboxed due to letterbox, with the ratio of the display.
+ assertTaskMaxBoundsSandboxed();
}
@Test
@@ -828,6 +847,14 @@
assertEquals(displayBounds.height(), taskBounds.height());
assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio),
taskBounds.width());
+ // New activity max bounds are sandboxed due to letterbox.
+ assertThat(newActivity.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(taskBounds);
+ // Task max bounds are sandboxed due to letterbox, with the ratio of the display.
+ assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds().height())
+ .isEqualTo(displayBounds.height());
+ assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds().width())
+ .isEqualTo(Math.round(displayBounds.height() / newActivity.info.maxAspectRatio));
// App bounds should be fullscreen in Task bounds.
assertFalse(newActivity.inSizeCompatMode());
@@ -856,6 +883,9 @@
assertFalse(mTask.isTaskLetterboxed());
assertScaled();
assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity);
+ assertThat(mActivity.inSizeCompatMode()).isTrue();
+ // Activity max bounds are sandboxed due to size compat mode.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
final Rect activityBounds = new Rect(mActivity.getBounds());
mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
@@ -866,6 +896,8 @@
assertScaled();
assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity);
assertEquals(activityBounds, mActivity.getBounds());
+ // Activity max bounds are sandboxed due to size compat.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
}
@Test
@@ -881,6 +913,7 @@
// In Task letterbox
assertTrue(mTask.isTaskLetterboxed());
assertFalse(mActivity.inSizeCompatMode());
+ assertTaskMaxBoundsSandboxed();
// Rotate display to portrait.
rotateDisplay(display, ROTATION_90);
@@ -888,6 +921,7 @@
// App should be in size compat.
assertFalse(mTask.isTaskLetterboxed());
assertScaled();
+ assertActivityMaxBoundsSandboxedForSizeCompat();
// Rotate display to landscape.
rotateDisplay(display, ROTATION_180);
@@ -895,6 +929,7 @@
// In Task letterbox
assertTrue(mTask.isTaskLetterboxed());
assertFalse(mActivity.inSizeCompatMode());
+ assertTaskMaxBoundsSandboxed();
}
@Test
@@ -912,20 +947,26 @@
// In Task letterbox
assertTrue(mTask.isTaskLetterboxed());
assertFalse(mActivity.inSizeCompatMode());
+ // Task is letterboxed due to mismatched orientation request.
+ assertTaskMaxBoundsSandboxed();
- // Rotate display to portrait.
+ // Rotate display to landscape.
rotateDisplay(display, ROTATION_90);
// App should be in size compat.
assertFalse(mTask.isTaskLetterboxed());
assertScaled();
+ // Activity max bounds are sandboxed due to unresizable app.
+ assertActivityMaxBoundsSandboxedForSizeCompat();
- // Rotate display to landscape.
+ // Rotate display to portrait.
rotateDisplay(display, ROTATION_180);
// In Task letterbox
assertTrue(mTask.isTaskLetterboxed());
assertFalse(mActivity.inSizeCompatMode());
+ // Task is letterboxed, as in first case.
+ assertTaskMaxBoundsSandboxed();
}
@Test
@@ -942,12 +983,18 @@
assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation);
assertEquals(2800, displayBounds.width());
assertEquals(1400, displayBounds.height());
- taskDisplayArea.setBounds(0, 0, 2400, 1000);
+ Rect displayAreaBounds = new Rect(0, 0, 2400, 1000);
+ taskDisplayArea.setBounds(displayAreaBounds);
final Rect activityBounds = new Rect(mActivity.getBounds());
assertFalse(mActivity.inSizeCompatMode());
assertEquals(2400, activityBounds.width());
assertEquals(1000, activityBounds.height());
+ // Task and activity maximum bounds inherit from TaskDisplayArea bounds.
+ assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(displayAreaBounds);
+ assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(displayAreaBounds);
}
@Test
@@ -1020,9 +1067,9 @@
displayPolicy.onConfigurationChanged();
final TestWindowToken token = createTestWindowToken(
- WindowManager.LayoutParams.TYPE_STATUS_BAR, displayContent);
+ TYPE_STATUS_BAR, displayContent);
final WindowManager.LayoutParams attrs =
- new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_STATUS_BAR);
+ new WindowManager.LayoutParams(TYPE_STATUS_BAR);
attrs.gravity = android.view.Gravity.TOP;
attrs.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -1095,6 +1142,48 @@
assertFalse(mActivity.hasSizeCompatBounds());
}
+ /** Asserts both the activity and task max bounds inherit from the TaskDisplayArea. */
+ private void assertMaxBoundsInheritDisplayAreaBounds() {
+ final Rect taskDisplayAreaBounds = mTask.getDisplayArea().getBounds();
+ assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(taskDisplayAreaBounds);
+ assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(taskDisplayAreaBounds);
+ }
+
+ /**
+ * Asserts task-level letterboxing, so both activity and task max bounds
+ * are sandboxed to the letterbox bounds.
+ */
+ private void assertTaskMaxBoundsSandboxed() {
+ // Activity inherits max bounds from task, since sandboxing applied to task.
+ assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(mTask.getBounds());
+ // Task max bounds are sandboxed due to letterbox.
+ assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(mTask.getBounds());
+ }
+
+ /** Asserts activity-level size compat mode, so only activity max bounds are sandboxed. */
+ private void assertActivityMaxBoundsSandboxedForSizeCompat() {
+ // Activity max bounds are sandboxed due to size compat mode.
+ assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(mActivity.getWindowConfiguration().getBounds());
+ // Task inherits max bounds from display.
+ assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(mTask.getDisplayContent().getBounds());
+ }
+
+ /** Asserts activity-level letterboxing, so only activity max bounds are sandboxed. */
+ private void assertActivityMaxBoundsSandboxedForLetterbox() {
+ // Activity is sandboxed due to fixed aspect ratio.
+ assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(mActivity.getBounds());
+ // Task inherits bounds from display.
+ assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds())
+ .isEqualTo(mTask.getDisplayContent().getBounds());
+ }
+
static Configuration rotateDisplay(DisplayContent display, int rotation) {
final Configuration c = new Configuration();
display.getDisplayRotation().setRotation(rotation);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index d49956a..9372530 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -145,7 +145,7 @@
assertThat(surface).isNotNull();
verify(session).addToDisplay(any(), argThat(this::isTrustedOverlay), anyInt(), anyInt(),
- any(), any(), any(), any(), any());
+ any(), any(), any(), any());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index ae85ceb..d71993d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -136,6 +136,11 @@
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
final TestDisplayContent newDisplay = createInternal(display);
+ // Ensure letterbox aspect ratio is not overridden on any device target.
+ // {@link com.android.internal.R.dimen.config_taskLetterboxAspectRatio}, provided by
+ // the below method, is set on some device form factors.
+ mService.mWindowManager.setTaskLetterboxAspectRatio(0);
+
// disable the normal system decorations
final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy();
spyOn(displayPolicy);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 53cea5a..d1d0ac6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -47,6 +47,7 @@
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.InsetsState;
+import android.view.RoundedCorners;
import android.view.Surface;
import android.view.WindowManager;
@@ -146,7 +147,7 @@
final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
final DisplayFrames displayFrames = new DisplayFrames(dc.getDisplayId(), new InsetsState(),
- info, cutout);
+ info, cutout, RoundedCorners.NO_ROUNDED_CORNERS);
wallpaperWindowToken.applyFixedRotationTransform(info, displayFrames, config);
// Check that the wallpaper has the same frame in landscape than in portrait
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index 1607f01..a1f89ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -275,7 +275,7 @@
imeSource.setFrame(imeFrame);
imeSource.setVisible(true);
w.updateRequestedVisibility(state);
- w.mBehindIme = true;
+ w.mAboveInsetsState.addSource(imeSource);
// With no insets or system decor all the frames incoming from PhoneWindowManager
// are identical.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 8b604a3..3492d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -340,7 +340,7 @@
final WindowState w = new WindowState(service, session, iWindow, token, parent,
OP_NONE, attrs, VISIBLE, ownerId, userId,
- ownerCanAddInternalSystemWindow, false /* ownerCanUseBackgroundBlur */,
+ ownerCanAddInternalSystemWindow,
powerManagerWrapper);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
@@ -1213,8 +1213,7 @@
TestWindowState(WindowManagerService service, Session session, IWindow window,
WindowManager.LayoutParams attrs, WindowToken token) {
super(service, session, window, token, null, OP_NONE, attrs, 0, 0, 0,
- false /* ownerCanAddInternalSystemWindow */,
- false /* ownerCanUseBackgroundBlur */);
+ false /* ownerCanAddInternalSystemWindow */);
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c1fb74d..be36f82 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1782,7 +1782,7 @@
}
@Override
- public void onPackageModified(String pkgName) {
+ public void onPackageModified(@NonNull String pkgName) {
// If the package modified is not in the current user, then don't bother making
// any changes as we are going to do any initialization needed when we switch users.
if (mCurUser != getChangingUserId()) {
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index eaa3e04..5cb0c17 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -80,7 +80,7 @@
}
namespace {
-void CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets>& assets,
+void CompileApkAssetsLayouts(const std::unique_ptr<android::ApkAssets>& assets,
CompilationTarget target, std::ostream& target_out) {
android::AssetManager2 resources;
resources.SetApkAssets({assets.get()});
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index b73ef9b..be5fae4 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.net.Uri;
@@ -67,7 +68,8 @@
* Sets the participants for the resulting {@link ConnectionRequest}
* @param participants The participants to which the {@link Connection} is to connect.
*/
- public @NonNull Builder setParticipants(@Nullable List<Uri> participants) {
+ public @NonNull Builder setParticipants(
+ @SuppressLint("NullableCollection") @Nullable List<Uri> participants) {
this.mParticipants = participants;
return this;
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 6dc096d..88ef1b0 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -333,6 +333,8 @@
void cleanupStuckCalls();
+ void resetCarMode();
+
void setTestDefaultCallRedirectionApp(String packageName);
void setTestPhoneAcctSuggestionComponent(String flattenedComponentName);
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 8507d85..706e3cb 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -344,6 +344,7 @@
// TODO: Instead of doing this, we should create a formal way for cloning cell identity.
// Cell identity is not an immutable object so we have to deep copy it.
mCellIdentity = CellIdentity.CREATOR.createFromParcel(p);
+ p.recycle();
}
if (nri.mVoiceSpecificInfo != null) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e4386e75..65b8de2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14242,7 +14242,7 @@
* If this policy is enabled, data will be temporarily enabled on the non-default data SIM
* during any voice calls.
*
- * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabledStatus}.
+ * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabled}.
* @hide
*/
@SystemApi
@@ -14256,7 +14256,7 @@
* will also return true for {@link ApnSetting#TYPE_MMS}.
* When disabled, the MMS APN will be governed by the same rules as all other APNs.
*
- * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabledStatus}.
+ * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabled}.
* @hide
*/
@SystemApi
@@ -14284,11 +14284,11 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void setMobileDataPolicyEnabledStatus(@MobileDataPolicy int policy, boolean enabled) {
+ public void setMobileDataPolicyEnabled(@MobileDataPolicy int policy, boolean enabled) {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.setMobileDataPolicyEnabledStatus(getSubId(), policy, enabled);
+ service.setMobileDataPolicyEnabled(getSubId(), policy, enabled);
}
} catch (RemoteException ex) {
// This could happen if binder process crashes.
diff --git a/telephony/java/android/telephony/ims/DelegateStateCallback.java b/telephony/java/android/telephony/ims/DelegateStateCallback.java
index fb65949..6bf992e 100644
--- a/telephony/java/android/telephony/ims/DelegateStateCallback.java
+++ b/telephony/java/android/telephony/ims/DelegateStateCallback.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.telephony.ims.stub.SipDelegate;
import android.telephony.ims.stub.SipTransportImplBase;
@@ -52,7 +53,9 @@
* implementing this feature elsewhere. If all features of this {@link SipDelegate} are
* denied, this method should still be called.
*/
- void onCreated(@NonNull SipDelegate delegate, @Nullable Set<FeatureTagState> deniedTags);
+ void onCreated(@NonNull SipDelegate delegate,
+ @SuppressLint("NullableCollection") // TODO(b/154763999): Mark deniedTags @Nonnull
+ @Nullable Set<FeatureTagState> deniedTags);
/**
* This must be called by the ImsService after the framework calls
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index f444c62..31ba853 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,6 +25,7 @@
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.WorkerThread;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -1325,7 +1326,7 @@
* the RCS VoLTE single registration feature. Only default messaging application may receive
* the intent.
*
- * <p>Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to specify the subscription index for which
+ * <p>Contains {@link #EXTRA_SUBSCRIPTION_ID} to specify the subscription index for which
* the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration
* status.
*/
@@ -1371,7 +1372,7 @@
* provisioning is done using autoconfiguration, then these parameters shall be
* sent in the HTTP get request to fetch the RCS provisioning. RCS client
* configuration must be provided by the application before registering for the
- * provisioning status events {@link #registerRcsProvisioningChangedCallback()}
+ * provisioning status events {@link #registerRcsProvisioningChangedCallback}
* @param rcc RCS client configuration {@link RcsClientConfiguration}
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1387,13 +1388,15 @@
}
/**
- * Returns a flag to indicate if the device software and the carrier
- * have the capability to support RCS Volte single IMS registration.
- * @return true if this single registration is capable, false otherwise
+ * Returns a flag to indicate whether or not the device supports IMS single registration for
+ * MMTEL and RCS features as well as if the carrier has provisioned the feature.
+ * @return true if IMS single registration is capable at this time, or false otherwise
* @throws ImsException If the remote ImsService is not available for
* any reason or the subscription associated with this instance is no
* longer active. See {@link ImsException#getCode()} for more
* information.
+ * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION for whether or not this
+ * device supports IMS single registration.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
@@ -1430,7 +1433,7 @@
* available. This can happen if the service crashed, for example.
* It shall also throw this exception when the RCS client parameters for the
* application are not valid. In that case application must set the client
- * params (See {@link #setRcsClientConfiguration()}) and re register the
+ * params (See {@link #setRcsClientConfiguration}) and re register the
* callback.
* See {@link ImsException#getCode()} for a more detailed reason.
*/
@@ -1458,9 +1461,9 @@
* will result in a no-op.
* @param callback The existing {@link RcsProvisioningCallback} to be
* removed.
- * @see #registerRcsProvisioningChangedCallback(RcsClientConfiguration,
- * Executor, RcsProvisioningCallback) @throws IllegalArgumentException
- * if the subscription associated with this callback is invalid.
+ * @see #registerRcsProvisioningChangedCallback
+ * @throws IllegalArgumentException if the subscription associated with this callback is
+ * invalid.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void unregisterRcsProvisioningChangedCallback(
diff --git a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
index eddbb10..8762b6a 100644
--- a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
+++ b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
@@ -280,6 +280,12 @@
"sip_config_path_header_string";
/**
+ * The SIP User-Agent header value used by the IMS stack during IMS registration.
+ */
+ public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING =
+ "sip_config_sip_user_agent_header_string";
+
+ /**
* SIP User part string in contact header
*/
public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING =
@@ -292,12 +298,20 @@
"sip_config_p_access_network_info_header_string";
/**
- * SIP P-last-access-network-info header string
+ * The SIP P-last-access-network-info header value, populated for networks that require this
+ * information to be provided in outgoing SIP messages.
*/
public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING =
"sip_config_p_last_access_network_info_header_string";
/**
+ * The Cellular-Network-Info header value (See 3GPP 24.229, section 7.2.15), populated for
+ * networks that require this information to be provided as part of outgoing SIP messages.
+ */
+ public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING =
+ "sip_config_cellular_network_info_header_string";
+
+ /**
* SIP P-associated-uri header string
*/
public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING =
@@ -320,9 +334,11 @@
KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING,
KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING,
KEY_SIP_CONFIG_PATH_HEADER_STRING,
+ KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING,
KEY_SIP_CONFIG_URI_USER_PART_STRING,
KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING,
KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING,
+ KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING,
KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java
index 2e9eb94..04421c9 100644
--- a/telephony/java/android/telephony/ims/SipDelegateManager.java
+++ b/telephony/java/android/telephony/ims/SipDelegateManager.java
@@ -24,6 +24,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.telephony.BinderCacheManager;
@@ -47,6 +48,9 @@
* This allows multiple IMS applications to forward SIP messages to/from their application for the
* purposes of providing a single IMS registration to the carrier's IMS network from potentially
* many IMS stacks implementing a subset of the supported MMTEL/RCS features.
+ * <p>
+ * This API is only supported if the device supports the
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION} feature.
* @hide
*/
@SystemApi
@@ -269,6 +273,7 @@
* {@link ImsException#getCode()} for more information.
*
* @see CarrierConfigManager.Ims#KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL
+ * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean isSupported() throws ImsException {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 22aa18f..0cd17da3 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2172,7 +2172,7 @@
*/
String getMmsUAProfUrl(int subId);
- void setMobileDataPolicyEnabledStatus(int subscriptionId, int policy, boolean enabled);
+ void setMobileDataPolicyEnabled(int subscriptionId, int policy, boolean enabled);
boolean isMobileDataPolicyEnabled(int subscriptionId, int policy);
@@ -2360,6 +2360,16 @@
*/
String getContactFromEab(String contact);
+ /*
+ * Check whether the device supports RCS User Capability Exchange or not.
+ */
+ boolean getDeviceUceEnabled();
+
+ /*
+ * Set the device supports RCS User Capability Exchange.
+ */
+ void setDeviceUceEnabled(boolean isEnabled);
+
/**
* Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the
* specified thresholds.
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 02c75ed..39dc9c2 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -16,6 +16,7 @@
name: "ApkVerityTest",
srcs: ["src/**/*.java"],
libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+ static_libs: ["frameworks-base-hostutils"],
test_suites: ["general-tests", "vts"],
target_required: [
"block_device_writer_module",
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 629b6c7..d0eb9be 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -21,10 +21,10 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.RootPermissionTest;
+import com.android.fsverity.AddFsVerityCertRule;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
@@ -35,6 +35,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -85,40 +86,25 @@
private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der";
- private static final String APK_VERITY_STANDARD_MODE = "2";
-
/** Only 4K page is supported by fs-verity currently. */
private static final int FSVERITY_PAGE_SIZE = 4096;
+ @Rule
+ public final AddFsVerityCertRule mAddFsVerityCertRule =
+ new AddFsVerityCertRule(this, CERT_PATH);
+
private ITestDevice mDevice;
- private String mKeyId;
@Before
public void setUp() throws DeviceNotAvailableException {
mDevice = getDevice();
- String apkVerityMode = mDevice.getProperty("ro.apk_verity.mode");
- assumeTrue(mDevice.getLaunchApiLevel() >= 30
- || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
-
- mKeyId = expectRemoteCommandToSucceed(
- "mini-keyctl padd asymmetric fsv_test .fs-verity < " + CERT_PATH).trim();
- if (!mKeyId.matches("^\\d+$")) {
- String keyId = mKeyId;
- mKeyId = null;
- fail("Key ID is not decimal: " + keyId);
- }
-
uninstallPackage(TARGET_PACKAGE);
}
@After
public void tearDown() throws DeviceNotAvailableException {
uninstallPackage(TARGET_PACKAGE);
-
- if (mKeyId != null) {
- expectRemoteCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
- }
}
@Test
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index c945aea..0792e8b 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -18,28 +18,7 @@
name: "FlickerTests",
srcs: ["src/**/*.java", "src/**/*.kt"],
manifest: "AndroidManifest.xml",
- test_config: "AndroidTestPhysicalDevices.xml",
- platform_apis: true,
- certificate: "platform",
- test_suites: ["device-tests"],
- libs: ["android.test.runner"],
- static_libs: [
- "androidx.test.ext.junit",
- "flickertestapplib",
- "flickerlib",
- "truth-prebuilt",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
- "platform-test-annotations",
- ],
-}
-
-
-android_test {
- name: "FlickerTestsVirtual",
- srcs: ["src/**/*.java", "src/**/*.kt"],
- manifest: "AndroidManifest.xml",
- test_config: "AndroidTestVirtualDevices.xml",
+ test_config: "AndroidTest.xml",
platform_apis: true,
certificate: "platform",
test_suites: ["device-tests"],
diff --git a/tests/FlickerTests/AndroidTestPhysicalDevices.xml b/tests/FlickerTests/AndroidTest.xml
similarity index 95%
rename from tests/FlickerTests/AndroidTestPhysicalDevices.xml
rename to tests/FlickerTests/AndroidTest.xml
index b1cee5c..e68fbd8 100644
--- a/tests/FlickerTests/AndroidTestPhysicalDevices.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -25,7 +25,6 @@
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.server.wm.flicker"/>
- <option name="include-annotation" value="androidx.test.filters.RequiresDevice" />
<option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6600s" />
diff --git a/tests/FlickerTests/AndroidTestVirtualDevices.xml b/tests/FlickerTests/AndroidTestVirtualDevices.xml
deleted file mode 100644
index 9a5413a..0000000
--- a/tests/FlickerTests/AndroidTestVirtualDevices.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2018 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Flicker Tests">
- <option name="test-tag" value="FlickerTests" />
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <!-- keeps the screen on during tests -->
- <option name="screen-always-on" value="on" />
- <!-- prevents the phone from restarting -->
- <option name="force-skip-system-props" value="true" />
- <!-- set WM tracing verbose level to all -->
- <option name="run-command" value="cmd window tracing level all" />
- <!-- inform WM to log all transactions -->
- <option name="run-command" value="cmd window tracing transaction" />
- <!-- restart launcher to activate TAPL -->
- <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner">
- <!-- reboot the device to teardown any crashed tests -->
- <option name="cleanup-action" value="REBOOT" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="FlickerTests.apk"/>
- <option name="test-file-name" value="FlickerTestApp.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.server.wm.flicker"/>
- <option name="exclude-annotation" value="androidx.test.filters.RequiresDevice" />
- <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
- <option name="shell-timeout" value="6600s" />
- <option name="test-timeout" value="6000s" />
- <option name="hidden-api-checks" value="false" />
- </test>
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/sdcard/flicker" />
- <option name="collect-on-run-ended-only" value="true" />
- <option name="clean-up" value="true" />
- </metrics_collector>
-</configuration>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index ba12fbe..89c6663 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -17,37 +17,40 @@
package com.android.server.wm.flicker
import android.platform.helpers.IAppHelper
-import com.android.server.wm.flicker.dsl.EventLogAssertionBuilder
-import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
-import com.android.server.wm.flicker.dsl.WmAssertionBuilder
+import com.android.server.wm.flicker.dsl.EventLogAssertionBuilderLegacy
+import com.android.server.wm.flicker.dsl.LayersAssertionBuilderLegacy
+import com.android.server.wm.flicker.dsl.WmAssertionBuilderLegacy
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_WINDOW_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME
-const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
-const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
const val WALLPAPER_TITLE = "Wallpaper"
@JvmOverloads
-fun WmAssertionBuilder.statusBarWindowIsAlwaysVisible(
+fun WmAssertionBuilderLegacy.statusBarWindowIsAlwaysVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
all("statusBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
+ this.showsAboveAppWindow(STATUS_BAR_WINDOW_NAME)
}
}
@JvmOverloads
-fun WmAssertionBuilder.navBarWindowIsAlwaysVisible(
+fun WmAssertionBuilderLegacy.navBarWindowIsAlwaysVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
all("navBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsAboveAppWindow(NAV_BAR_WINDOW_NAME)
}
}
-fun WmAssertionBuilder.visibleWindowsShownMoreThanOneConsecutiveEntry(
+fun WmAssertionBuilderLegacy.visibleWindowsShownMoreThanOneConsecutiveEntry(
ignoreWindows: List<String> = emptyList(),
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -57,7 +60,7 @@
}
}
-fun WmAssertionBuilder.launcherReplacesAppWindowAsTopWindow(
+fun WmAssertionBuilderLegacy.launcherReplacesAppWindowAsTopWindow(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -69,7 +72,7 @@
}
}
-fun WmAssertionBuilder.wallpaperWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.wallpaperWindowBecomesVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -80,7 +83,7 @@
}
}
-fun WmAssertionBuilder.wallpaperWindowBecomesInvisible(
+fun WmAssertionBuilderLegacy.wallpaperWindowBecomesInvisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -91,7 +94,7 @@
}
}
-fun WmAssertionBuilder.appWindowAlwaysVisibleOnTop(
+fun WmAssertionBuilderLegacy.appWindowAlwaysVisibleOnTop(
packageName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -101,7 +104,7 @@
}
}
-fun WmAssertionBuilder.appWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.appWindowBecomesVisible(
appName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -113,8 +116,20 @@
}
}
+fun WmAssertionBuilderLegacy.appWindowBecomesInVisible(
+ appName: String,
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("appWindowBecomesInVisible", bugId, enabled) {
+ this.showsAppWindow(appName)
+ .then()
+ .hidesAppWindow(appName)
+ }
+}
+
@JvmOverloads
-fun LayersAssertionBuilder.noUncoveredRegions(
+fun LayersAssertionBuilderLegacy.noUncoveredRegions(
beginRotation: Int,
endRotation: Int = beginRotation,
allStates: Boolean = true,
@@ -144,49 +159,49 @@
}
@JvmOverloads
-fun LayersAssertionBuilder.navBarLayerIsAlwaysVisible(
+fun LayersAssertionBuilderLegacy.navBarLayerIsAlwaysVisible(
rotatesScreen: Boolean = false,
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
if (rotatesScreen) {
all("navBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsLayer(NAV_BAR_LAYER_NAME)
.then()
- .hidesLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ .hidesLayer(NAV_BAR_LAYER_NAME)
.then()
- .showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ .showsLayer(NAV_BAR_LAYER_NAME)
}
} else {
all("navBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsLayer(NAV_BAR_LAYER_NAME)
}
}
}
@JvmOverloads
-fun LayersAssertionBuilder.statusBarLayerIsAlwaysVisible(
+fun LayersAssertionBuilderLegacy.statusBarLayerIsAlwaysVisible(
rotatesScreen: Boolean = false,
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
if (rotatesScreen) {
all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+ this.showsLayer(STATUS_BAR_LAYER_NAME)
.then()
- hidesLayer(STATUS_BAR_WINDOW_TITLE)
+ hidesLayer(STATUS_BAR_LAYER_NAME)
.then()
- .showsLayer(STATUS_BAR_WINDOW_TITLE)
+ .showsLayer(STATUS_BAR_LAYER_NAME)
}
} else {
all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+ this.showsLayer(STATUS_BAR_LAYER_NAME)
}
}
}
@JvmOverloads
-fun LayersAssertionBuilder.navBarLayerRotatesAndScales(
+fun LayersAssertionBuilderLegacy.navBarLayerRotatesAndScales(
beginRotation: Int,
endRotation: Int = beginRotation,
bugId: Int = 0,
@@ -196,21 +211,21 @@
val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
start("navBarLayerRotatesAndScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
}
end("navBarLayerRotatesAndScales_EndingPost", bugId, enabled) {
- this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+ this.hasVisibleRegion(NAV_BAR_LAYER_NAME, endingPos)
}
if (startingPos == endingPos) {
all("navBarLayerRotatesAndScales", enabled = false, bugId = 167747321) {
- this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
}
}
}
@JvmOverloads
-fun LayersAssertionBuilder.statusBarLayerRotatesScales(
+fun LayersAssertionBuilderLegacy.statusBarLayerRotatesScales(
beginRotation: Int,
endRotation: Int = beginRotation,
bugId: Int = 0,
@@ -220,14 +235,14 @@
val endingPos = WindowUtils.getStatusBarPosition(endRotation)
start("statusBarLayerRotatesScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, startingPos)
}
end("statusBarLayerRotatesScales_EndingPos", bugId, enabled) {
- this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos)
+ this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, endingPos)
}
}
-fun LayersAssertionBuilder.visibleLayersShownMoreThanOneConsecutiveEntry(
+fun LayersAssertionBuilderLegacy.visibleLayersShownMoreThanOneConsecutiveEntry(
ignoreLayers: List<String> = emptyList(),
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -237,7 +252,7 @@
}
}
-fun LayersAssertionBuilder.appLayerReplacesWallpaperLayer(
+fun LayersAssertionBuilderLegacy.appLayerReplacesWallpaperLayer(
appName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -249,7 +264,7 @@
}
}
-fun LayersAssertionBuilder.wallpaperLayerReplacesAppLayer(
+fun LayersAssertionBuilderLegacy.wallpaperLayerReplacesAppLayer(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -261,7 +276,7 @@
}
}
-fun LayersAssertionBuilder.layerAlwaysVisible(
+fun LayersAssertionBuilderLegacy.layerAlwaysVisible(
packageName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -271,7 +286,7 @@
}
}
-fun LayersAssertionBuilder.layerBecomesVisible(
+fun LayersAssertionBuilderLegacy.layerBecomesVisible(
packageName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -283,7 +298,7 @@
}
}
-fun LayersAssertionBuilder.layerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.layerBecomesInvisible(
packageName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -295,7 +310,7 @@
}
}
-fun EventLogAssertionBuilder.focusChanges(
+fun EventLogAssertionBuilderLegacy.focusChanges(
vararg windows: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -305,7 +320,7 @@
}
}
-fun EventLogAssertionBuilder.focusDoesNotChange(
+fun EventLogAssertionBuilderLegacy.focusDoesNotChange(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index b569eda..1a47449 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -41,6 +41,9 @@
wmHelper.waitForRotation(rotation)
wmHelper.waitForNavBarStatusBarVisible()
wmHelper.waitForAppTransitionIdle()
+
+ // Ensure WindowManagerService wait until all animations have completed
+ instrumentation.getUiAutomation().syncInputTransactions()
} catch (e: RemoteException) {
throw RuntimeException(e)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index 96c2009..c775cb8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -17,13 +17,13 @@
package com.android.server.wm.flicker.ime
import android.platform.helpers.IAppHelper
-import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
-import com.android.server.wm.flicker.dsl.WmAssertionBuilder
+import com.android.server.wm.flicker.dsl.LayersAssertionBuilderLegacy
+import com.android.server.wm.flicker.dsl.WmAssertionBuilderLegacy
const val IME_WINDOW_TITLE = "InputMethod"
@JvmOverloads
-fun LayersAssertionBuilder.imeLayerBecomesVisible(
+fun LayersAssertionBuilderLegacy.imeLayerBecomesVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -34,7 +34,7 @@
}
}
-fun LayersAssertionBuilder.imeLayerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.imeLayerBecomesInvisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -45,7 +45,7 @@
}
}
-fun LayersAssertionBuilder.imeAppLayerIsAlwaysVisible(
+fun LayersAssertionBuilderLegacy.imeAppLayerIsAlwaysVisible(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -55,7 +55,7 @@
}
}
-fun WmAssertionBuilder.imeAppWindowIsAlwaysVisible(
+fun WmAssertionBuilderLegacy.imeAppWindowIsAlwaysVisible(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -65,7 +65,7 @@
}
}
-fun WmAssertionBuilder.imeWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.imeWindowBecomesVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -76,7 +76,7 @@
}
}
-fun WmAssertionBuilder.imeWindowBecomesInvisible(
+fun WmAssertionBuilderLegacy.imeWindowBecomesInvisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
@@ -87,7 +87,7 @@
}
}
-fun WmAssertionBuilder.imeAppWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.imeAppWindowBecomesVisible(
windowName: String,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -99,7 +99,7 @@
}
}
-fun WmAssertionBuilder.imeAppWindowBecomesInvisible(
+fun WmAssertionBuilderLegacy.imeAppWindowBecomesInvisible(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
@@ -111,7 +111,7 @@
}
}
-fun LayersAssertionBuilder.imeAppLayerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.imeAppLayerBecomesInvisible(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
index ba2ee5f..1bd1190 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
@@ -17,9 +17,9 @@
package com.android.server.wm.flicker.launch
import android.platform.helpers.IAppHelper
-import com.android.server.wm.flicker.dsl.WmAssertionBuilder
+import com.android.server.wm.flicker.dsl.WmAssertionBuilderLegacy
-fun WmAssertionBuilder.appWindowReplacesLauncherAsTopWindow(
+fun WmAssertionBuilderLegacy.appWindowReplacesLauncherAsTopWindow(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 104758d..5381009 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -98,6 +98,8 @@
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
private Context mSpyContext;
+ // Keep track of all created watchdogs to apply device config changes
+ private List<PackageWatchdog> mAllocatedWatchdogs;
@Mock
private ConnectivityModuleConnector mConnectivityModuleConnector;
@Mock
@@ -112,7 +114,8 @@
MockitoAnnotations.initMocks(this);
new File(InstrumentationRegistry.getContext().getFilesDir(),
"package-watchdog.xml").delete();
- adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG);
+ adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.WRITE_DEVICE_CONFIG);
mTestLooper = new TestLooper();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
@@ -157,12 +160,23 @@
return storedValue == null ? defaultValue : Long.parseLong(storedValue);
}
).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
+ Boolean.toString(true), false);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT), false);
+
+ mAllocatedWatchdogs = new ArrayList<>();
}
@After
public void tearDown() throws Exception {
dropShellPermissions();
mSession.finishMocking();
+ mAllocatedWatchdogs.clear();
}
@Test
@@ -611,10 +625,6 @@
*/
@Test
public void testExplicitHealthCheckStateChanges() throws Exception {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
-
TestController controller = new TestController();
PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
@@ -807,9 +817,6 @@
/** Test default values are used when device property is invalid. */
@Test
public void testInvalidConfig_watchdogTriggerFailureCount() {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
Integer.toString(-1), /*makeDefault*/false);
@@ -835,9 +842,6 @@
/** Test default values are used when device property is invalid. */
@Test
public void testInvalidConfig_watchdogTriggerDurationMillis() {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
Integer.toString(2), /*makeDefault*/false);
@@ -850,7 +854,6 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -862,7 +865,6 @@
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -917,9 +919,6 @@
/** Test we are notified when enough failures are triggered within any window. */
@Test
public void testFailureTriggerWindow() {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
Integer.toString(3), /*makeDefault*/false);
@@ -933,11 +932,9 @@
// Raise 2 failures at t=0 and t=900 respectively
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
moveTimeForwardAndDispatch(900);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
// Raise 2 failures at t=1100
moveTimeForwardAndDispatch(200);
@@ -1303,15 +1300,15 @@
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
Boolean.toString(enabled), /*makeDefault*/false);
- //give time for DeviceConfig to broadcast the property value change
- try {
- Thread.sleep(SHORT_DURATION);
- } catch (InterruptedException e) {
- fail("Thread.sleep unexpectedly failed!");
+ // Call updateConfigs() so device config changes take effect immediately
+ for (PackageWatchdog watchdog : mAllocatedWatchdogs) {
+ watchdog.updateConfigs();
}
}
private void moveTimeForwardAndDispatch(long milliSeconds) {
+ // Exhaust all due runnables now which shouldn't be executed after time-leap
+ mTestLooper.dispatchAll();
mTestClock.moveTimeForward(milliSeconds);
mTestLooper.moveTimeForward(milliSeconds);
mTestLooper.dispatchAll();
@@ -1354,6 +1351,7 @@
verify(mConnectivityModuleConnector).registerHealthListener(
mConnectivityModuleCallbackCaptor.capture());
}
+ mAllocatedWatchdogs.add(watchdog);
return watchdog;
}
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
new file mode 100644
index 0000000..d809fe8
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -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.
+
+java_test_host {
+ name: "UpdatableSystemFontTest",
+ srcs: ["src/**/*.java"],
+ libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+ static_libs: ["frameworks-base-hostutils"],
+ test_suites: ["general-tests", "vts"],
+ data: [
+ ":NotoColorEmojiTtf",
+ ":UpdatableSystemFontTestCertDer",
+ ":UpdatableSystemFontTestNotoColorEmojiTtfFsvSig",
+ ":UpdatableSystemFontTestNotoColorEmojiV1Ttf",
+ ":UpdatableSystemFontTestNotoColorEmojiV1TtfFsvSig",
+ ":UpdatableSystemFontTestNotoColorEmojiV2Ttf",
+ ":UpdatableSystemFontTestNotoColorEmojiV2TtfFsvSig",
+ ],
+}
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
new file mode 100644
index 0000000..efe5d70
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<configuration description="Updatable system font integration/regression test">
+ <option name="test-suite-tag" value="apct" />
+
+ <!-- This test requires root to side load fs-verity cert. -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <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" />
+ <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV1.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf" />
+ <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig" />
+ <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV2.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf" />
+ <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="UpdatableSystemFontTest.jar" />
+ </test>
+</configuration>
diff --git a/tests/UpdatableSystemFontTest/OWNERS b/tests/UpdatableSystemFontTest/OWNERS
new file mode 100644
index 0000000..34ac813
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 24939
+
+include /graphics/java/android/graphics/fonts/OWNERS
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
new file mode 100644
index 0000000..6d161a5
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.updatablesystemfont;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.RootPermissionTest;
+
+import com.android.fsverity.AddFsVerityCertRule;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests if fonts can be updated by 'cmd font'.
+ */
+@RootPermissionTest
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
+
+ private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der";
+
+ private static final Pattern PATTERN_FONT = Pattern.compile("path = ([^, \n]*)");
+ private static final String NOTO_COLOR_EMOJI_TTF = "NotoColorEmoji.ttf";
+ private static final String TEST_NOTO_COLOR_EMOJI_V1_TTF =
+ "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf";
+ private static final String TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG =
+ "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig";
+ private static final String TEST_NOTO_COLOR_EMOJI_V2_TTF =
+ "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf";
+ private static final String TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG =
+ "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig";
+ private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF =
+ "/data/local/tmp/NotoColorEmoji.ttf";
+ private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG =
+ "/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig";
+
+ @Rule
+ public final AddFsVerityCertRule mAddFsverityCertRule =
+ new AddFsVerityCertRule(this, CERT_PATH);
+
+ @Before
+ public void setUp() throws Exception {
+ expectRemoteCommandToSucceed("cmd font clear");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ expectRemoteCommandToSucceed("cmd font clear");
+ }
+
+ @Test
+ public void updateFont() 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/");
+ }
+
+ @Test
+ public void updateFont_twice() 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);
+ expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V2_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+ String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPath2).startsWith("/data/fonts/files/");
+ assertThat(fontPath2).isNotEqualTo(fontPath);
+ }
+
+ @Test
+ public void updatedFont_dataFileIsImmutableAndReadable() 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");
+
+ expectRemoteCommandToFail("echo -n '' >> " + fontPath);
+ expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
+ }
+
+ @Test
+ public void updateFont_invalidCert() throws Exception {
+ expectRemoteCommandToFail(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+ }
+
+ @Test
+ public void updateFont_downgradeFromSystem() throws Exception {
+ expectRemoteCommandToFail(String.format("cmd font update %s %s",
+ ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG));
+ }
+
+ @Test
+ public void updateFont_downgradeFromData() throws Exception {
+ expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V2_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+ expectRemoteCommandToFail(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+ }
+
+ private String getFontPath(String fontFileName) throws Exception {
+ // TODO: add a dedicated command for testing.
+ String lines = expectRemoteCommandToSucceed("cmd font dump");
+ for (String line : lines.split("\n")) {
+ Matcher m = PATTERN_FONT.matcher(line);
+ if (m.find() && m.group(1).endsWith(fontFileName)) {
+ return m.group(1);
+ }
+ }
+ CLog.e("Font not found: " + fontFileName);
+ return null;
+ }
+
+ private String expectRemoteCommandToSucceed(String cmd) throws Exception {
+ CommandResult result = getDevice().executeShellV2Command(cmd);
+ assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
+ .that(result.getStatus())
+ .isEqualTo(CommandStatus.SUCCESS);
+ return result.getStdout();
+ }
+
+ private void expectRemoteCommandToFail(String cmd) throws Exception {
+ CommandResult result = getDevice().executeShellV2Command(cmd);
+ assertWithMessage("Unexpected success from `" + cmd + "`: " + result.getStderr())
+ .that(result.getStatus())
+ .isNotEqualTo(CommandStatus.SUCCESS);
+ }
+}
diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp
new file mode 100644
index 0000000..1296699
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -0,0 +1,82 @@
+// 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.
+
+filegroup {
+ name: "UpdatableSystemFontTestKeyPem",
+ srcs: ["UpdatableSystemFontTestKey.pem"],
+}
+
+filegroup {
+ name: "UpdatableSystemFontTestCertPem",
+ srcs: ["UpdatableSystemFontTestCert.pem"],
+}
+
+filegroup {
+ name: "UpdatableSystemFontTestCertDer",
+ srcs: ["UpdatableSystemFontTestCert.der"],
+}
+
+genrule_defaults {
+ name: "updatable_system_font_increment_font_revision_default",
+ tools: ["update_font_metadata"],
+ cmd: "$(location update_font_metadata) " +
+ "--input=$(in) " +
+ "--output=$(out) " +
+ "--revision=+1",
+}
+
+genrule {
+ name: "UpdatableSystemFontTestNotoColorEmojiV1Ttf",
+ defaults: ["updatable_system_font_increment_font_revision_default"],
+ srcs: [":NotoColorEmojiTtf"],
+ out: ["UpdatableSystemFontTestNotoColorEmojiV1.ttf"],
+}
+
+genrule {
+ name: "UpdatableSystemFontTestNotoColorEmojiV2Ttf",
+ defaults: ["updatable_system_font_increment_font_revision_default"],
+ srcs: [":UpdatableSystemFontTestNotoColorEmojiV1Ttf"],
+ out: ["UpdatableSystemFontTestNotoColorEmojiV2.ttf"],
+}
+
+genrule_defaults {
+ name: "updatable_system_font_sig_gen_default",
+ tools: ["fsverity"],
+ tool_files: [":UpdatableSystemFontTestKeyPem", ":UpdatableSystemFontTestCertPem"],
+ cmd: "$(location fsverity) sign $(in) $(out) " +
+ "--key=$(location :UpdatableSystemFontTestKeyPem) " +
+ "--cert=$(location :UpdatableSystemFontTestCertPem) " +
+ "> /dev/null",
+}
+
+genrule {
+ name: "UpdatableSystemFontTestNotoColorEmojiTtfFsvSig",
+ defaults: ["updatable_system_font_sig_gen_default"],
+ srcs: [":NotoColorEmojiTtf"],
+ out: ["UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig"],
+}
+
+genrule {
+ name: "UpdatableSystemFontTestNotoColorEmojiV1TtfFsvSig",
+ defaults: ["updatable_system_font_sig_gen_default"],
+ srcs: [":UpdatableSystemFontTestNotoColorEmojiV1Ttf"],
+ out: ["UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig"],
+}
+
+genrule {
+ name: "UpdatableSystemFontTestNotoColorEmojiV2TtfFsvSig",
+ defaults: ["updatable_system_font_sig_gen_default"],
+ srcs: [":UpdatableSystemFontTestNotoColorEmojiV2Ttf"],
+ out: ["UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig"],
+}
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der
new file mode 100644
index 0000000..f7aa15f
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem
new file mode 100644
index 0000000..0cd1f66
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFOTCCAyGgAwIBAgIUFaI1D5NtwkCVM3G4bFZ6sQSb598wDQYJKoZIhvcNAQEL
+BQAwLDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdBbmRyb2lk
+MB4XDTIxMDIwMTA3MzAyNFoXDTIxMDMwMzA3MzAyNFowLDELMAkGA1UEBhMCVVMx
+CzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdBbmRyb2lkMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA0U1zptc41E65ooeBPD33Mjgp6cYPydyj2Acq80Xy7lP7
+d6/2t6w7nNNl2x3n8dAhOl3de3IxTp6JI2SdoRb7obfcp+hWJoo/cxnHpr3q/u4R
+KED0rmWaOVHpGbajSTFZgN+cTbTKJbgtXm/H65x1QfO18ep/vj5fRiu1xPpJqDv/
+xuvuko2U3eC2+NayxzCWXVFrKPLx8GvzSQ3Utaug17vs7/5GqkRJgq3lk4DvmjNA
+vY8YA4RAkII1sSaceAWFEG6ztENLu2kjcxAI9qHxxBwQZit/NtFVlFGqSN3MEYjS
+M9Fz04RsUxF672QJpAgwCJDZ41rdB3hkHvOUK9PcepBsHdZq9cQ+E64+TX+jsJLu
+VouViKlYr6WYjvhfqZeRhwbj7CoEZ2DyEZKrl27fgWaidUT5LGEQLVxg90ymbimI
+6UwXRUwmRBQJBdRO4RGvngtqxRuamyjAKDDHx5YccXCX4FWLUypyQlz0asojvbJZ
+Og7DFa1qsRdGrGIRoQJ8pYnAjBJfSudr1l0mR7fZSfZc0W9ZmuROWx9Ip7aJWQnQ
+8JLtbNPuFLD2qbmg9Y1lcXJp1FvI9FcM8JsBqZNEANQwwsdTaa8gw+3W6J2SXKQP
+H+yZI/fJWWWRFADtqmpxtvXK9K+Cy1HQmg7D0IIxVPp8rrbz6TGrg+R3/N9FBWMC
+AwEAAaNTMFEwHQYDVR0OBBYEFDS48o2UAstoOyLMcgamHKrZdl3cMB8GA1UdIwQY
+MBaAFDS48o2UAstoOyLMcgamHKrZdl3cMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggIBAF7taBYAe20tWZu0pY9d4Z8il4LoJcRrKF4YiA02UizErgCF
+h4iECy6+pcu7DJUfvCh3dCWE7CDG+OnfUWTwEHVG9n8XI/ydetBUG76PZwTadI7B
+gzJ1y7/vWqJo5U6ki+sXNmq3hkgNsNZgza3LpdovkWJYeRdffM6m/bimzwYx9id8
+5mKw2PcbVZcb25r+0dCoLVJsqqCoRjdYUy/MKPutWG2bPzmaIv8KsKFN+mzlwhJH
+lpJ/LR+3NoaHrOCFG7CW/2Ihe501vmdQ2m/VKosyk0igw8WmTsY6xMbw2t77yKkD
+hnJr1NbhKeEV9gAB2BFX8nRWI7NTgp8fG78YLVz1UcbIHmYLgFoc3ezyma+CoR86
+ER20lKd4+TNnz4RtaPdZlBa0Ba3bsMtEneqlrHvcPrZ5tgGsQR9+cy3ZtTZ/LUQX
++Xuj/EoJXuuB3hkhg52zawN5n7WUe8efWHcv1jHqeIj0phcgbZ6u4fFBPsYjzDKe
+VuYHXglNOchmoBQwEaJI/TCiEgI8dcSJXSquLAXrtznVnxzT46ZMEt5LaW1/1NLx
+q//yoPdolCI0lpunh5jvIZJpUl5XMjxVSyaveQDNVqJkITWzWqIxAT5yTLtkCNlW
+c1XyzeHkpMItiJtBruExmnaTmNjlVKsXP8wQFOYbDGgXY5iHIMbgovptRyH/
+-----END CERTIFICATE-----
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem
new file mode 100644
index 0000000..09bb104
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDRTXOm1zjUTrmi
+h4E8PfcyOCnpxg/J3KPYByrzRfLuU/t3r/a3rDuc02XbHefx0CE6Xd17cjFOnokj
+ZJ2hFvuht9yn6FYmij9zGcemver+7hEoQPSuZZo5UekZtqNJMVmA35xNtMoluC1e
+b8frnHVB87Xx6n++Pl9GK7XE+kmoO//G6+6SjZTd4Lb41rLHMJZdUWso8vHwa/NJ
+DdS1q6DXu+zv/kaqREmCreWTgO+aM0C9jxgDhECQgjWxJpx4BYUQbrO0Q0u7aSNz
+EAj2ofHEHBBmK3820VWUUapI3cwRiNIz0XPThGxTEXrvZAmkCDAIkNnjWt0HeGQe
+85Qr09x6kGwd1mr1xD4Trj5Nf6Owku5Wi5WIqVivpZiO+F+pl5GHBuPsKgRnYPIR
+kquXbt+BZqJ1RPksYRAtXGD3TKZuKYjpTBdFTCZEFAkF1E7hEa+eC2rFG5qbKMAo
+MMfHlhxxcJfgVYtTKnJCXPRqyiO9slk6DsMVrWqxF0asYhGhAnylicCMEl9K52vW
+XSZHt9lJ9lzRb1ma5E5bH0intolZCdDwku1s0+4UsPapuaD1jWVxcmnUW8j0Vwzw
+mwGpk0QA1DDCx1NpryDD7dbonZJcpA8f7Jkj98lZZZEUAO2qanG29cr0r4LLUdCa
+DsPQgjFU+nyutvPpMauD5Hf830UFYwIDAQABAoICAQCTVTcFCdl6MdSg4UwK0P/S
+fRCb/A0fJs67Agis6N9h/wI0NUyx7G6mLXU0si+U29KYGH0RKcgltJmKrYf8XoZR
+R3DvTTBfvs99QXd2G5hxTboMIPVcUi8nDE7PB+6XVkLP4hhP5uSpeqWNJZiQdTlh
+bKH2IgE8NQGyDpDMkPcKkvmw2GG/DiTtrwJ91fxRFRWzqN2LHMFMYWEHWtIR9Der
+xSC7q72om5s3fxvtIkUHwe5fwXvA9fbRAqezBR/9qL0LXTHowbpsuUz38SCuJD9g
+sfSlRxcsyly4pGf/FQpSiYKWcWlcSopKSzLDkyLqMc1GKlkGnu6aFJg95W63D1LS
+OaOXuYShHxLkqyhT8uQGRqDCu3E2ivb6fMxAPzJxxs3JrZvumNsqyxbp+HVF0idj
+NijMN/8Kb4KmNHG9I3SHG61tQFYDtxoMMNiHzq3fafBJnVcf6iThQdE5pGLN2OdF
+3rcSTeHI2HxhTrXtuiHmWXNk9aZ2TOhrssNZZjDkFL/KYh6G8guy+tn66YWy77VC
+id+6PBzYXTXsUauo4NWW1rLUfzT/y93IwVGpoXs7GHUUduZ6Q3PxsMTMF9IjBvqR
+JvfP84CUfGXoebVJmWyGhtW6N5ParvQxbonDitA0TPZcRyX3N8yqIGO/mb8MWA9M
+7s/xMZuksOpw5LSCoTAW6QKCAQEA9Z1WM/5XT+5HkwPBvS5aRz0SqMqPxAkZ0dNz
+O06zpXv6e2IPy40UzFWCIkyq3vWKQ5bqU1fnejUdmjvtnP+KhH6fxnQCgiunnrDq
+j1Sk2Y4gb1KyZY/C8IejOexM2qX7sfDTLI8XEvxJxVFCNmvYfnv4AX5QD8VOsjrg
+bvodLAgDSo2FVDP+mkpW7zAIoRV02l6QdZA+YcG940eqxB9sPR3/1KUHe9wUTTbJ
+FV5ahEPuXCyvRJkZ/rD5CPPZoQHfjKHxDlu7yfoatzCSYj10R+RInfqOFGVY4D/C
+2csXjymwTN4CFUnJcP410YhPFn5ekmc/E3xqPKgIDQhQ2+oivwKCAQEA2icN1iEo
+YuBJwB3pX3jrwk+1bpUXASueWhyAhSeNMrTrJ/lSgEAy9FBxJNnK1PjkABRnFhS+
+uxbC2hQdfAkNDS21PQOk6hOhebUVuBdfmKY1CL+P9y4Af0fjV1rgRGHihnZB5lKU
+1R/wFb8c4QgPwduiqDoZ5QP3dgxpZ4R3SCzuoyVelUSlgyWPoslg+yPddcnV8bTf
+BUlEOOyXgVudkSFlRpWZ+/ZAkLTj8rnrtKp8/+GtTQvJvPJHPfuSsDhI+EQVWqab
+HelMhxvIi9xOyN/OCc3Ex6JlEVa+X8EyNhQsV2sAKnld92hdEozW2uxRniIkxvL4
+CuBp/p3fWxEaXQKCAQEAw2j7RYCMnNZZ8Zhiko4HW3g2mT4XpYMMHMlbe4sBGJ8L
+yRBaurqzGmLJl1ph8+NsrpuqMMbWLn+F3sjhIjCZVxKbMbvopwHuaS4eYAya30/Z
+dFhaAL2g/dccQSBEgQzftFGC4YeydvNsCeW9hSjGZNNinGWPcwyqsNhw6Tpq7TYu
+0CjKNBTt8nlEsyYHJ4m3n2jvC+nIB+Spm+LP9Rt+9R0iBl+KFbwiFtCIqUyZPXQC
+dylCBJS+fsj0SXAg7J1d6ziIXcEUJfyrNqYZQLneAriYIcBPO+DqFfgEoVyYkNk9
+H9rd02wSLajCzsLhEWdW/KnSIEGzEDErvpqoIl8kZwKCAQEAjNO3T+sZyjKmCXqF
+xBcogsi4BAoEzsGcuOk7Yjn1Ia2/PI/r3VUUT7l6QOLD2JZPgWmqXovH0LjR0rw3
+iHHDViWSoS+wD1fa3tmyiqO0F7P7+ojHZDbzJTeAIE1PB3X1KP5AbnITGD5E25UD
+DJYKrgeeSmEvhDL6Vd+PT78ozZQL/Y/LLishebcOsXS0wYsWlMpV7XHoot34R5Mb
+/urond7kJRvASvJeHcxYdsHk0j1Y8kp6eIlKk0oICZBU0qOTH4m8C0gQTM/lkjay
+UO9IgM5RkOyfwowoGHhZ7zClvFlrgodVlRXCPkvGAYqfzLXPvnimKzSAQW07n53E
+qWIyFQKCAQAWRNG6hPCOzkNxMJ/RK7dwxZW6b4a1L3PMXTT/xrBKRIS1WNjbpkYO
+/FLIufOqJT6FQN2obM5uso3TI+R7MwH8DnTSnDDy0Hvs3CdHdtn2tapZOViF/UVv
+uCQa+/jMVKFCZ8k7pPFMIG6tB6WBA5MmJrW+8s0ouxLbRF5rZyzqOeyPdVBYYYDb
+68nGNA6GAtTQs9h7xV2tsQ1bXNP+6gqG3BgZYo+76xKITddT6s9aaC++LVBnPOdq
+LHV7gUvoBkVLjIp4L3Fb/DGMCcOVMCxmFlRBn+RBlV7slehvgq+Ywz2GHWLr+O/z
+V2NAtwvCfZE2Do/4f2mpHnamhS6AvrDe
+-----END PRIVATE KEY-----
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index ef973ac..861d221 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -182,6 +182,24 @@
@SmallTest
@Test
+ public void setFunctionsNcmAndRndis() {
+ final long rndisPlusNcm = UsbManager.FUNCTION_RNDIS | UsbManager.FUNCTION_NCM;
+
+ mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+ UsbManager.FUNCTION_NCM));
+ assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+ mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+ rndisPlusNcm));
+ assertEquals(rndisPlusNcm, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+ mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+ UsbManager.FUNCTION_NCM));
+ assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+ }
+
+ @SmallTest
+ @Test
public void enableAdb() {
sendBootCompleteMessages(mUsbHandler);
Message msg = mUsbHandler.obtainMessage(MSG_ENABLE_ADB);
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index f6a2846..ffde68e 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -36,7 +36,7 @@
"libvndksupport",
"libziparchive",
"libz",
- "netd_aidl_interface-cpp",
+ "netd_aidl_interface-V5-cpp",
],
}
@@ -53,6 +53,7 @@
jarjar_rules: "jarjar-rules.txt",
static_libs: [
"androidx.test.rules",
+ "bouncycastle-repackaged-unbundled",
"FrameworksNetCommonTests",
"frameworks-base-testutils",
"frameworks-net-integration-testutils",
diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java
index 076e41d..1abd39a 100644
--- a/tests/net/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java
@@ -30,7 +30,7 @@
import com.android.internal.net.VpnProfile;
import com.android.net.module.util.ProxyUtils;
-import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 68e5ce0..c7554f6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -132,6 +132,7 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.NotificationManager;
@@ -252,6 +253,7 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.net.module.util.ArrayTrackRecord;
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.connectivity.ConnectivityConstants;
import com.android.server.connectivity.MockableSystemProperties;
@@ -906,28 +908,69 @@
}
/**
- * A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove
- * operations have been processed. Before ConnectivityService can add or remove any requests,
- * the factory must be told to expect those operations by calling expectAddRequestsWithScores or
- * expectRemoveRequests.
+ * A NetworkFactory that allows to wait until any in-flight NetworkRequest add or remove
+ * operations have been processed and test for them.
*/
private static class MockNetworkFactory extends NetworkFactory {
private final ConditionVariable mNetworkStartedCV = new ConditionVariable();
private final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
- // Used to expect that requests be removed or added on a separate thread, without sleeping.
- // Callers can call either expectAddRequestsWithScores() or expectRemoveRequests() exactly
- // once, then cause some other thread to add or remove requests, then call
- // waitForRequests().
- // It is not possible to wait for both add and remove requests. When adding, the queue
- // contains the expected score. When removing, the value is unused, all matters is the
- // number of objects in the queue.
- private final LinkedBlockingQueue<Integer> mExpectations;
+ static class RequestEntry {
+ @NonNull
+ public final NetworkRequest request;
- // Whether we are currently expecting requests to be added or removed. Valid only if
- // mExpectations is non-empty.
- private boolean mExpectingAdditions;
+ RequestEntry(@NonNull final NetworkRequest request) {
+ this.request = request;
+ }
+
+ static final class Add extends RequestEntry {
+ public final int factorySerialNumber;
+
+ Add(@NonNull final NetworkRequest request, final int factorySerialNumber) {
+ super(request);
+ this.factorySerialNumber = factorySerialNumber;
+ }
+ }
+
+ static final class Remove extends RequestEntry {
+ Remove(@NonNull final NetworkRequest request) {
+ super(request);
+ }
+ }
+ }
+
+ // History of received requests adds and removes.
+ private final ArrayTrackRecord<RequestEntry>.ReadHead mRequestHistory =
+ new ArrayTrackRecord<RequestEntry>().newReadHead();
+
+ private static <T> T failIfNull(@Nullable final T obj, @Nullable final String message) {
+ if (null == obj) fail(null != message ? message : "Must not be null");
+ return obj;
+ }
+
+
+ public RequestEntry.Add expectRequestAdd() {
+ return failIfNull((RequestEntry.Add) mRequestHistory.poll(TIMEOUT_MS,
+ it -> it instanceof RequestEntry.Add), "Expected request add");
+ }
+
+ public void expectRequestAdds(final int count) {
+ for (int i = count; i > 0; --i) {
+ expectRequestAdd();
+ }
+ }
+
+ public RequestEntry.Remove expectRequestRemove() {
+ return failIfNull((RequestEntry.Remove) mRequestHistory.poll(TIMEOUT_MS,
+ it -> it instanceof RequestEntry.Remove), "Expected request remove");
+ }
+
+ public void expectRequestRemoves(final int count) {
+ for (int i = count; i > 0; --i) {
+ expectRequestRemove();
+ }
+ }
// Used to collect the networks requests managed by this factory. This is a duplicate of
// the internal information stored in the NetworkFactory (which is private).
@@ -936,7 +979,6 @@
public MockNetworkFactory(Looper looper, Context context, String logTag,
NetworkCapabilities filter) {
super(looper, context, logTag, filter);
- mExpectations = new LinkedBlockingQueue<>();
}
public int getMyRequestCount() {
@@ -970,95 +1012,33 @@
@Override
protected void handleAddRequest(NetworkRequest request, int score,
int factorySerialNumber) {
- synchronized (mExpectations) {
- final Integer expectedScore = mExpectations.poll(); // null if the queue is empty
-
- assertNotNull("Added more requests than expected (" + request + " score : "
- + score + ")", expectedScore);
- // If we're expecting anything, we must be expecting additions.
- if (!mExpectingAdditions) {
- fail("Can't add requests while expecting requests to be removed");
- }
- if (expectedScore != score) {
- fail("Expected score was " + expectedScore + " but actual was " + score
- + " in added request");
- }
-
- // Add the request.
- mNetworkRequests.put(request.requestId, request);
- super.handleAddRequest(request, score, factorySerialNumber);
- mExpectations.notify();
- }
+ mNetworkRequests.put(request.requestId, request);
+ super.handleAddRequest(request, score, factorySerialNumber);
+ mRequestHistory.add(new RequestEntry.Add(request, factorySerialNumber));
}
@Override
protected void handleRemoveRequest(NetworkRequest request) {
- synchronized (mExpectations) {
- final Integer expectedScore = mExpectations.poll(); // null if the queue is empty
+ mNetworkRequests.remove(request.requestId);
+ super.handleRemoveRequest(request);
+ mRequestHistory.add(new RequestEntry.Remove(request));
+ }
- assertTrue("Removed more requests than expected", expectedScore != null);
- // If we're expecting anything, we must be expecting removals.
- if (mExpectingAdditions) {
- fail("Can't remove requests while expecting requests to be added");
- }
+ public void assertRequestCountEquals(final int count) {
+ assertEquals(count, getMyRequestCount());
+ }
- // Remove the request.
- mNetworkRequests.remove(request.requestId);
- super.handleRemoveRequest(request);
- mExpectations.notify();
- }
+ @Override
+ public void terminate() {
+ super.terminate();
+ // Make sure there are no remaining requests unaccounted for.
+ assertNull(mRequestHistory.poll(TIMEOUT_MS, r -> true));
}
// Trigger releasing the request as unfulfillable
public void triggerUnfulfillable(NetworkRequest r) {
super.releaseRequestAsUnfulfillableByAnyFactory(r);
}
-
- private void assertNoExpectations() {
- if (mExpectations.size() != 0) {
- fail("Can't add expectation, " + mExpectations.size() + " already pending");
- }
- }
-
- // Expects that requests with the specified scores will be added.
- public void expectAddRequestsWithScores(final int... scores) {
- assertNoExpectations();
- mExpectingAdditions = true;
- for (int score : scores) {
- mExpectations.add(score);
- }
- }
-
- // Expects that count requests will be removed.
- public void expectRemoveRequests(final int count) {
- assertNoExpectations();
- mExpectingAdditions = false;
- for (int i = 0; i < count; ++i) {
- mExpectations.add(0); // For removals the score is ignored so any value will do.
- }
- }
-
- // Waits for the expected request additions or removals to happen within a timeout.
- public void waitForRequests() throws InterruptedException {
- final long deadline = SystemClock.elapsedRealtime() + TIMEOUT_MS;
- synchronized (mExpectations) {
- while (mExpectations.size() > 0 && SystemClock.elapsedRealtime() < deadline) {
- mExpectations.wait(deadline - SystemClock.elapsedRealtime());
- }
- }
- final long count = mExpectations.size();
- final String msg = count + " requests still not " +
- (mExpectingAdditions ? "added" : "removed") +
- " after " + TIMEOUT_MS + " ms";
- assertEquals(msg, 0, count);
- }
-
- public SparseArray<NetworkRequest> waitForNetworkRequests(final int count)
- throws InterruptedException {
- waitForRequests();
- assertEquals(count, getMyRequestCount());
- return mNetworkRequests;
- }
}
private Set<UidRange> uidRangesForUid(int uid) {
@@ -1283,32 +1263,10 @@
}
private void updateUidNetworkingBlocked() {
- // Changes the return value of the mock NetworkPolicyManager's isUidNetworkingBlocked method
- // based on the current UID rules and restrict background setting. Note that the test never
- // pretends to be a foreground app, so always declare no connectivity if background
- // networking is not allowed.
- switch (mUidRules) {
- case RULE_REJECT_ALL:
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), anyBoolean()))
- .thenReturn(true);
- break;
-
- case RULE_REJECT_METERED:
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), eq(true)))
- .thenReturn(true);
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), eq(false)))
- .thenReturn(mRestrictBackground);
- break;
-
- case RULE_ALLOW_METERED:
- case RULE_NONE:
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), anyBoolean()))
- .thenReturn(mRestrictBackground);
- break;
-
- default:
- fail("Unknown policy rule " + mUidRules);
- }
+ doAnswer(i -> NetworkPolicyManagerInternal.isUidNetworkingBlocked(
+ i.getArgument(0) /* uid */, mUidRules, i.getArgument(1) /* metered */,
+ mRestrictBackground)
+ ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
}
private void setUidRulesChanged(int uidRules) throws RemoteException {
@@ -2617,12 +2575,6 @@
callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
}
- private int[] makeIntArray(final int size, final int value) {
- final int[] array = new int[size];
- Arrays.fill(array, value);
- return array;
- }
-
private void tryNetworkFactoryRequests(int capability) throws Exception {
// Verify NOT_RESTRICTED is set appropriately
final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability)
@@ -2644,9 +2596,9 @@
mServiceContext, "testFactory", filter);
testFactory.setScoreFilter(40);
ConditionVariable cv = testFactory.getNetworkStartedCV();
- testFactory.expectAddRequestsWithScores(0);
testFactory.register();
- testFactory.waitForNetworkRequests(1);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
int expectedRequestCount = 1;
NetworkCallback networkCallback = null;
// For non-INTERNET capabilities we cannot rely on the default request being present, so
@@ -2655,13 +2607,12 @@
assertFalse(testFactory.getMyStartRequested());
NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
networkCallback = new NetworkCallback();
- testFactory.expectAddRequestsWithScores(0); // New request
mCm.requestNetwork(request, networkCallback);
expectedRequestCount++;
- testFactory.waitForNetworkRequests(expectedRequestCount);
+ testFactory.expectRequestAdd();
}
waitFor(cv);
- assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ testFactory.assertRequestCountEquals(expectedRequestCount);
assertTrue(testFactory.getMyStartRequested());
// Now bring in a higher scored network.
@@ -2675,15 +2626,14 @@
// When testAgent connects, ConnectivityService will re-send us all current requests with
// the new score. There are expectedRequestCount such requests, and we must wait for all of
// them.
- testFactory.expectAddRequestsWithScores(makeIntArray(expectedRequestCount, 50));
testAgent.connect(false);
testAgent.addCapability(capability);
waitFor(cv);
- testFactory.waitForNetworkRequests(expectedRequestCount);
+ testFactory.expectRequestAdds(expectedRequestCount);
+ testFactory.assertRequestCountEquals(expectedRequestCount);
assertFalse(testFactory.getMyStartRequested());
// Bring in a bunch of requests.
- testFactory.expectAddRequestsWithScores(makeIntArray(10, 50));
assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
ConnectivityManager.NetworkCallback[] networkCallbacks =
new ConnectivityManager.NetworkCallback[10];
@@ -2693,24 +2643,24 @@
builder.addCapability(capability);
mCm.requestNetwork(builder.build(), networkCallbacks[i]);
}
- testFactory.waitForNetworkRequests(10 + expectedRequestCount);
+ testFactory.expectRequestAdds(10);
+ testFactory.assertRequestCountEquals(10 + expectedRequestCount);
assertFalse(testFactory.getMyStartRequested());
// Remove the requests.
- testFactory.expectRemoveRequests(10);
for (int i = 0; i < networkCallbacks.length; i++) {
mCm.unregisterNetworkCallback(networkCallbacks[i]);
}
- testFactory.waitForNetworkRequests(expectedRequestCount);
+ testFactory.expectRequestRemoves(10);
+ testFactory.assertRequestCountEquals(expectedRequestCount);
assertFalse(testFactory.getMyStartRequested());
// Drop the higher scored network.
cv = testFactory.getNetworkStartedCV();
- // With the default network disconnecting, the requests are sent with score 0 to factories.
- testFactory.expectAddRequestsWithScores(makeIntArray(expectedRequestCount, 0));
testAgent.disconnect();
waitFor(cv);
- testFactory.waitForNetworkRequests(expectedRequestCount);
+ testFactory.expectRequestAdds(expectedRequestCount);
+ testFactory.assertRequestCountEquals(expectedRequestCount);
assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
assertTrue(testFactory.getMyStartRequested());
@@ -2753,9 +2703,8 @@
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
mServiceContext, "testFactory", filter);
// Register the factory and don't be surprised when the default request arrives.
- testFactory.expectAddRequestsWithScores(0);
testFactory.register();
- testFactory.waitForNetworkRequests(1);
+ testFactory.expectRequestAdd();
testFactory.setScoreFilter(42);
testFactory.terminate();
@@ -3898,38 +3847,37 @@
testFactory.setScoreFilter(40);
// Register the factory and expect it to start looking for a network.
- testFactory.expectAddRequestsWithScores(0); // Score 0 as the request is not served yet.
testFactory.register();
try {
- testFactory.waitForNetworkRequests(1);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
assertTrue(testFactory.getMyStartRequested());
// Bring up wifi. The factory stops looking for a network.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
// Score 60 - 40 penalty for not validated yet, then 60 when it validates
- testFactory.expectAddRequestsWithScores(20, 60);
mWiFiNetworkAgent.connect(true);
- testFactory.waitForRequests();
+ // Default request and mobile always on request
+ testFactory.expectRequestAdds(2);
assertFalse(testFactory.getMyStartRequested());
- ContentResolver cr = mServiceContext.getContentResolver();
-
// Turn on mobile data always on. The factory starts looking again.
- testFactory.expectAddRequestsWithScores(0); // Always on requests comes up with score 0
setAlwaysOnNetworks(true);
- testFactory.waitForNetworkRequests(2);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(2);
+
assertTrue(testFactory.getMyStartRequested());
// Bring up cell data and check that the factory stops looking.
assertLength(1, mCm.getAllNetworks());
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
- testFactory.expectAddRequestsWithScores(10, 50); // Unvalidated, then validated
mCellNetworkAgent.connect(true);
cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
- testFactory.waitForNetworkRequests(2);
- assertFalse(
- testFactory.getMyStartRequested()); // Because the cell network outscores us.
+ testFactory.expectRequestAdds(2); // Unvalidated and validated
+ testFactory.assertRequestCountEquals(2);
+ // The cell network outscores the factory filter, so start is not requested.
+ assertFalse(testFactory.getMyStartRequested());
// Check that cell data stays up.
waitForIdle();
@@ -3937,9 +3885,8 @@
assertLength(2, mCm.getAllNetworks());
// Turn off mobile data always on and expect the request to disappear...
- testFactory.expectRemoveRequests(1);
setAlwaysOnNetworks(false);
- testFactory.waitForNetworkRequests(1);
+ testFactory.expectRequestRemove();
// ... and cell data to be torn down.
cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
@@ -4246,46 +4193,33 @@
testFactory.setScoreFilter(40);
// Register the factory and expect it to receive the default request.
- testFactory.expectAddRequestsWithScores(0);
testFactory.register();
- SparseArray<NetworkRequest> requests = testFactory.waitForNetworkRequests(1);
-
- assertEquals(1, requests.size()); // have 1 request at this point
- int origRequestId = requests.valueAt(0).requestId;
+ testFactory.expectRequestAdd();
// Now file the test request and expect it.
- testFactory.expectAddRequestsWithScores(0);
mCm.requestNetwork(nr, networkCallback);
- requests = testFactory.waitForNetworkRequests(2); // have 2 requests at this point
+ final NetworkRequest newRequest = testFactory.expectRequestAdd().request;
- int newRequestId = 0;
- for (int i = 0; i < requests.size(); ++i) {
- if (requests.valueAt(i).requestId != origRequestId) {
- newRequestId = requests.valueAt(i).requestId;
- break;
- }
- }
-
- testFactory.expectRemoveRequests(1);
if (preUnregister) {
mCm.unregisterNetworkCallback(networkCallback);
// Simulate the factory releasing the request as unfulfillable: no-op since
// the callback has already been unregistered (but a test that no exceptions are
// thrown).
- testFactory.triggerUnfulfillable(requests.get(newRequestId));
+ testFactory.triggerUnfulfillable(newRequest);
} else {
// Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
- testFactory.triggerUnfulfillable(requests.get(newRequestId));
+ testFactory.triggerUnfulfillable(newRequest);
networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
- testFactory.waitForRequests();
// unregister network callback - a no-op (since already freed by the
// on-unavailable), but should not fail or throw exceptions.
mCm.unregisterNetworkCallback(networkCallback);
}
+ testFactory.expectRequestRemove();
+
testFactory.terminate();
handlerThread.quit();
}
@@ -6917,7 +6851,7 @@
cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
mCellNetworkAgent);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
- assertEquals(null, mCm.getActiveNetwork());
+ assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -6930,17 +6864,21 @@
setUidRulesChanged(RULE_NONE);
cellNetworkCallback.assertNoCallback();
- // Restrict the network based on BackgroundRestricted.
+ // Restrict background data. Networking is not blocked because the network is unmetered.
setRestrictBackgroundChanged(true);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
- assertEquals(null, mCm.getActiveNetwork());
+ assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
-
setRestrictBackgroundChanged(true);
cellNetworkCallback.assertNoCallback();
- setRestrictBackgroundChanged(false);
+
+ setUidRulesChanged(RULE_ALLOW_METERED);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+
+ setRestrictBackgroundChanged(false);
cellNetworkCallback.assertNoCallback();
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
@@ -8417,13 +8355,14 @@
private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
throws Exception {
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
+ mMockVpn.setVpnType(vpnType);
mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid);
- mMockVpn.setVpnType(vpnType);
final UnderlyingNetworkInfo underlyingNetworkInfo =
new UnderlyingNetworkInfo(vpnOwnerUid, VPN_IFNAME, new ArrayList<String>());
mMockVpn.setUnderlyingNetworkInfo(underlyingNetworkInfo);
+ when(mDeps.getConnectionOwnerUid(anyInt(), any(), any())).thenReturn(42);
}
private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
@@ -8472,8 +8411,7 @@
final int myUid = Process.myUid();
setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE);
- // TODO: Test the returned UID
- mService.getConnectionOwnerUid(getTestConnectionInfo());
+ assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo()));
}
@Test
@@ -8483,8 +8421,7 @@
mServiceContext.setPermission(
android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
- // TODO: Test the returned UID
- mService.getConnectionOwnerUid(getTestConnectionInfo());
+ assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo()));
}
@Test
@@ -8495,8 +8432,7 @@
mServiceContext.setPermission(
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED);
- // TODO: Test the returned UID
- mService.getConnectionOwnerUid(getTestConnectionInfo());
+ assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo()));
}
private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 68aaaed..f478282 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -148,6 +148,7 @@
managedProfileA.profileGroupId = primaryUser.id;
}
+ static final Network EGRESS_NETWORK = new Network(101);
static final String EGRESS_IFACE = "wlan0";
static final String TEST_VPN_PKG = "com.testvpn.vpn";
private static final String TEST_VPN_SERVER = "1.2.3.4";
@@ -963,7 +964,7 @@
InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
lp.addRoute(defaultRoute);
- vpn.startLegacyVpn(vpnProfile, mKeyStore, lp);
+ vpn.startLegacyVpn(vpnProfile, mKeyStore, EGRESS_NETWORK, lp);
return vpn;
}
diff --git a/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
new file mode 100644
index 0000000..5ab4dc6
--- /dev/null
+++ b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.fsverity;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.rules.ExternalResource;
+
+public final class AddFsVerityCertRule extends ExternalResource {
+
+ private static final String APK_VERITY_STANDARD_MODE = "2";
+
+ private final BaseHostJUnit4Test mHost;
+ private final String mCertPath;
+ private String mKeyId;
+
+ public AddFsVerityCertRule(BaseHostJUnit4Test host, String certPath) {
+ mHost = host;
+ mCertPath = certPath;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ ITestDevice device = mHost.getDevice();
+ String apkVerityMode = device.getProperty("ro.apk_verity.mode");
+ assumeTrue(device.getLaunchApiLevel() >= 30
+ || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
+
+ String keyId = executeCommand(
+ "mini-keyctl padd asymmetric fsv_test .fs-verity < " + mCertPath).trim();
+ assertThat(keyId).matches("^\\d+$");
+ mKeyId = keyId;
+ }
+
+ @Override
+ protected void after() {
+ if (mKeyId == null) return;
+ try {
+ executeCommand("mini-keyctl unlink " + mKeyId + " .fs-verity");
+ } catch (DeviceNotAvailableException e) {
+ LogUtil.CLog.e(e);
+ }
+ mKeyId = null;
+ }
+
+ private String executeCommand(String cmd) throws DeviceNotAvailableException {
+ CommandResult result = mHost.getDevice().executeShellV2Command(cmd);
+ assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
+ .that(result.getStatus())
+ .isEqualTo(CommandStatus.SUCCESS);
+ return result.getStdout();
+ }
+}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index e26bf19..e32e1e8 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -30,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -39,16 +39,20 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.Transport;
+import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnConfigTest;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -180,7 +184,7 @@
doAnswer((invocation) -> {
// Mock-within a doAnswer is safe, because it doesn't actually run nested.
return mock(Vcn.class);
- }).when(mMockDeps).newVcn(any(), any(), any());
+ }).when(mMockDeps).newVcn(any(), any(), any(), any());
final PersistableBundle bundle =
PersistableBundleUtils.fromMap(
@@ -253,7 +257,14 @@
verify(mConfigReadWriteHelper).readFromDisk();
}
- private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
+ private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+ Set<ParcelUuid> activeSubscriptionGroups) {
+ return triggerSubscriptionTrackerCbAndGetSnapshot(
+ activeSubscriptionGroups, Collections.emptyMap());
+ }
+
+ private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+ Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) {
final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
@@ -267,8 +278,14 @@
argThat(val -> activeSubscriptionGroups.contains(val)),
eq(TEST_PACKAGE_NAME));
+ doAnswer(invocation -> {
+ return subIdToGroupMap.get(invocation.getArgument(0));
+ }).when(snapshot).getGroupForSubId(anyInt());
+
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
cb.onNewSnapshot(snapshot);
+
+ return snapshot;
}
private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
@@ -287,21 +304,25 @@
@Test
public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
- triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
- verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
+ TelephonySubscriptionSnapshot snapshot =
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+ verify(mMockDeps)
+ .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot));
}
@Test
public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception {
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+ mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
- triggerSubscriptionTrackerCallback(Collections.emptySet());
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
// Verify teardown after delay
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
mTestLooper.dispatchAll();
verify(vcn).teardownAsynchronously();
+ verify(mMockPolicyListener).onPolicyChanged();
}
@Test
@@ -311,13 +332,13 @@
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
// Simulate SIM unloaded
- triggerSubscriptionTrackerCallback(Collections.emptySet());
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
// Simulate new SIM loaded right during teardown delay.
mTestLooper.moveTimeForward(
VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
mTestLooper.dispatchAll();
- triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2));
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
// Verify that even after the full timeout duration, the VCN instance is not torn down
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -331,7 +352,7 @@
final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
// Simulate SIM unloaded
- triggerSubscriptionTrackerCallback(Collections.emptySet());
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
// Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
// vcnInstance.
@@ -372,6 +393,7 @@
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
fail("Expected security exception for non system user");
} catch (SecurityException expected) {
+ verify(mMockPolicyListener, never()).onPolicyChanged();
}
}
@@ -383,6 +405,7 @@
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
fail("Expected security exception for missing carrier privileges");
} catch (SecurityException expected) {
+ verify(mMockPolicyListener, never()).onPolicyChanged();
}
}
@@ -392,6 +415,7 @@
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage");
fail("Expected exception due to mismatched packages in config and method call");
} catch (IllegalArgumentException expected) {
+ verify(mMockPolicyListener, never()).onPolicyChanged();
}
}
@@ -456,7 +480,12 @@
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
// Verify Vcn is started
- verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG));
+ verify(mMockDeps)
+ .newVcn(
+ eq(mVcnContext),
+ eq(TEST_UUID_2),
+ eq(TEST_VCN_CONFIG),
+ eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT));
// Verify Vcn is updated if it was previously started
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
@@ -496,14 +525,73 @@
mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
}
+ private void setUpVcnSubscription(int subId, ParcelUuid subGroup) {
+ mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
+ triggerSubscriptionTrackerCbAndGetSnapshot(
+ Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
+ }
+
+ private void verifyMergedNetworkCapabilitiesIsVcnManaged(
+ NetworkCapabilities mergedCapabilities, @Transport int transportType) {
+ assertTrue(mergedCapabilities.hasTransport(transportType));
+ assertFalse(
+ mergedCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+ }
+
@Test
- public void testGetUnderlyingNetworkPolicy() throws Exception {
+ 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(
- new NetworkCapabilities(), new LinkProperties());
+ mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
assertFalse(policy.isTeardownRequested());
- assertNotNull(policy.getMergedNetworkCapabilities());
+ verifyMergedNetworkCapabilitiesIsVcnManaged(
+ policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR);
+ }
+
+ @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());
+
+ assertFalse(policy.isTeardownRequested());
+ verifyMergedNetworkCapabilitiesIsVcnManaged(
+ policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
+ }
+
+ @Test
+ public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
+ NetworkCapabilities nc =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+ .build();
+
+ VcnUnderlyingNetworkPolicy policy =
+ mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+ assertFalse(policy.isTeardownRequested());
+ assertEquals(nc, policy.getMergedNetworkCapabilities());
}
@Test(expected = SecurityException.class)
@@ -515,4 +603,35 @@
mVcnMgmtSvc.getUnderlyingNetworkPolicy(new NetworkCapabilities(), new LinkProperties());
}
+
+ @Test
+ public void testSubscriptionSnapshotUpdateNotifiesVcn() {
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+ final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns();
+ final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2);
+
+ TelephonySubscriptionSnapshot snapshot =
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
+
+ verify(vcnInstance).updateSubscriptionSnapshot(eq(snapshot));
+ }
+
+ @Test
+ public void testAddNewVcnUpdatesPolicyListener() throws Exception {
+ mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
+ verify(mMockPolicyListener).onPolicyChanged();
+ }
+
+ @Test
+ public void testRemoveVcnUpdatesPolicyListener() throws Exception {
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+ mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+
+ verify(mMockPolicyListener).onPolicyChanged();
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
new file mode 100644
index 0000000..1d459a3
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -0,0 +1,386 @@
+/*
+ * 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.vcn;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+import android.telephony.SubscriptionInfo;
+import android.util.ArraySet;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.UUID;
+
+public class UnderlyingNetworkTrackerTest {
+ private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+ private static final int INITIAL_SUB_ID_1 = 1;
+ private static final int INITIAL_SUB_ID_2 = 2;
+ private static final int UPDATED_SUB_ID = 3;
+
+ private static final Set<Integer> INITIAL_SUB_IDS =
+ new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2));
+ private static final Set<Integer> UPDATED_SUB_IDS =
+ new ArraySet<>(Arrays.asList(UPDATED_SUB_ID));
+
+ private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .build();
+ private static final NetworkCapabilities SUSPENDED_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder(INITIAL_NETWORK_CAPABILITIES)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .build();
+ private static final NetworkCapabilities UPDATED_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+
+ private static final LinkProperties INITIAL_LINK_PROPERTIES =
+ getLinkPropertiesWithName("initial_iface");
+ private static final LinkProperties UPDATED_LINK_PROPERTIES =
+ getLinkPropertiesWithName("updated_iface");
+
+ @Mock private Context mContext;
+ @Mock private VcnNetworkProvider mVcnNetworkProvider;
+ @Mock private ConnectivityManager mConnectivityManager;
+ @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+ @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb;
+ @Mock private Network mNetwork;
+
+ @Captor private ArgumentCaptor<RouteSelectionCallback> mRouteSelectionCallbackCaptor;
+
+ private TestLooper mTestLooper;
+ private VcnContext mVcnContext;
+ private UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+ mVcnContext = spy(new VcnContext(mContext, mTestLooper.getLooper(), mVcnNetworkProvider));
+ doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+ setupSystemService(
+ mContext,
+ mConnectivityManager,
+ Context.CONNECTIVITY_SERVICE,
+ ConnectivityManager.class);
+
+ when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS);
+
+ mUnderlyingNetworkTracker =
+ new UnderlyingNetworkTracker(
+ mVcnContext,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET),
+ mNetworkTrackerCb);
+ }
+
+ private static LinkProperties getLinkPropertiesWithName(String iface) {
+ LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(iface);
+ return linkProperties;
+ }
+
+ private SubscriptionInfo getSubscriptionInfoForSubId(int subId) {
+ SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
+ when(subInfo.getSubscriptionId()).thenReturn(subId);
+ return subInfo;
+ }
+
+ @Test
+ public void testNetworkCallbacksRegisteredOnStartup() {
+ // verify NetworkCallbacks registered when instantiated
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getWifiRequest()),
+ any(),
+ any(NetworkBringupCallback.class));
+ verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, INITIAL_SUB_IDS);
+
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getRouteSelectionRequest()),
+ any(),
+ any(RouteSelectionCallback.class));
+ }
+
+ private void verifyBackgroundCellRequests(
+ TelephonySubscriptionSnapshot snapshot,
+ ParcelUuid subGroup,
+ Set<Integer> expectedSubIds) {
+ verify(snapshot).getAllSubIdsInGroup(eq(subGroup));
+
+ for (final int subId : expectedSubIds) {
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getCellRequestForSubId(subId)),
+ any(),
+ any(NetworkBringupCallback.class));
+ }
+ }
+
+ @Test
+ public void testUpdateSubscriptionSnapshot() {
+ // Verify initial cell background requests filed
+ verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, INITIAL_SUB_IDS);
+
+ TelephonySubscriptionSnapshot subscriptionUpdate =
+ mock(TelephonySubscriptionSnapshot.class);
+ when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS);
+
+ mUnderlyingNetworkTracker.updateSubscriptionSnapshot(subscriptionUpdate);
+
+ // verify that initially-filed bringup requests are unregistered
+ verify(mConnectivityManager, times(INITIAL_SUB_IDS.size()))
+ .unregisterNetworkCallback(any(NetworkBringupCallback.class));
+ verifyBackgroundCellRequests(subscriptionUpdate, SUB_GROUP, UPDATED_SUB_IDS);
+ }
+
+ private NetworkRequest getWifiRequest() {
+ return getExpectedRequestBase()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ }
+
+ private NetworkRequest getCellRequestForSubId(int subId) {
+ return getExpectedRequestBase()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+ .build();
+ }
+
+ private NetworkRequest getRouteSelectionRequest() {
+ return getExpectedRequestBase().build();
+ }
+
+ private NetworkRequest.Builder getExpectedRequestBase() {
+ return new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
+
+ @Test
+ public void testTeardown() {
+ mUnderlyingNetworkTracker.teardown();
+
+ // Expect 3 NetworkBringupCallbacks to be unregistered: 1 for WiFi and 2 for Cellular (1x
+ // for each subId)
+ verify(mConnectivityManager, times(3))
+ .unregisterNetworkCallback(any(NetworkBringupCallback.class));
+ verify(mConnectivityManager).unregisterNetworkCallback(any(RouteSelectionCallback.class));
+ }
+
+ @Test
+ public void testUnderlyingNetworkRecordEquals() {
+ UnderlyingNetworkRecord recordA =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ UnderlyingNetworkRecord recordB =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ UnderlyingNetworkRecord recordC =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ UPDATED_NETWORK_CAPABILITIES,
+ UPDATED_LINK_PROPERTIES,
+ false /* isBlocked */);
+
+ assertEquals(recordA, recordB);
+ assertNotEquals(recordA, recordC);
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkChange() {
+ verifyRegistrationOnAvailableAndGetCallback();
+ }
+
+ private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback() {
+ return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
+ }
+
+ private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback(
+ NetworkCapabilities networkCapabilities) {
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getRouteSelectionRequest()),
+ any(),
+ mRouteSelectionCallbackCaptor.capture());
+
+ RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue();
+ cb.onAvailable(mNetwork);
+ cb.onCapabilitiesChanged(mNetwork, networkCapabilities);
+ cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
+ cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ networkCapabilities,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ return cb;
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ UPDATED_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ UPDATED_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onNetworkSuspended(mNetwork);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ SUSPENDED_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkResumed() {
+ RouteSelectionCallback cb =
+ verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
+
+ cb.onNetworkResumed(mNetwork);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForBlocked() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ true /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkLoss() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onLost(mNetwork);
+
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null);
+ }
+
+ @Test
+ public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+
+ // Verify no more calls to the UnderlyingNetworkTrackerCallback when the
+ // UnderlyingNetworkRecord does not actually change
+ verifyNoMoreInteractions(mNetworkTrackerCb);
+ }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
new file mode 100644
index 0000000..e20070e
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.vcn;
+
+import static android.net.IpSecManager.DIRECTION_IN;
+import static android.net.IpSecManager.DIRECTION_OUT;
+
+import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for VcnGatewayConnection.ConnectedState */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
+ private VcnIkeSession mIkeSession;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
+
+ mIkeSession = mGatewayConnection.buildIkeSession();
+ mGatewayConnection.setIkeSession(mIkeSession);
+
+ mGatewayConnection.transitionTo(mGatewayConnection.mConnectedState);
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void testEnterStateCreatesNewIkeSession() throws Exception {
+ verify(mDeps).newIkeSession(any(), any(), any(), any(), any());
+ }
+
+ @Test
+ public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
+ mGatewayConnection
+ .getUnderlyingNetworkTrackerCallback()
+ .onSelectedUnderlyingNetworkChanged(null);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+ verify(mIkeSession, never()).close();
+ }
+
+ @Test
+ public void testNewNetworkTriggersMigration() throws Exception {
+ mGatewayConnection
+ .getUnderlyingNetworkTrackerCallback()
+ .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+ verify(mIkeSession, never()).close();
+ verify(mIkeSession).setNetwork(TEST_UNDERLYING_NETWORK_RECORD_2.network);
+ }
+
+ @Test
+ public void testSameNetworkDoesNotTriggerMigration() throws Exception {
+ mGatewayConnection
+ .getUnderlyingNetworkTrackerCallback()
+ .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+ }
+
+ @Test
+ public void testCreatedTransformsAreApplied() throws Exception {
+ for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
+ getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
+ mTestLooper.dispatchAll();
+
+ verify(mIpSecSvc)
+ .applyTunnelModeTransform(
+ eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
+ }
+
+ assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+ }
+
+ @Test
+ public void testChildSessionClosedTriggersDisconnect() throws Exception {
+ getChildSessionCallback().onClosed();
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ }
+
+ @Test
+ public void testIkeSessionClosedTriggersDisconnect() throws Exception {
+ getIkeSessionCallback().onClosed();
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+ verify(mIkeSession).close();
+ }
+
+ // TODO: Add tests for childOpened() when ChildSessionConfiguration can be mocked or created
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 4ecd215..fbaae6f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -44,7 +44,8 @@
@Test
public void testEnterWhileNotRunningTriggersQuit() throws Exception {
final VcnGatewayConnection vgc =
- new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps);
+ new VcnGatewayConnection(
+ mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps);
vgc.setIsRunning(false);
vgc.transitionTo(vgc.mDisconnectedState);
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index d741e5c..bc6bee2 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -16,20 +16,36 @@
package com.android.server.vcn;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import android.annotation.NonNull;
-import android.content.Context;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.net.LinkProperties;
+import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.net.vcn.VcnTransportInfo;
+import android.net.wifi.WifiInfo;
import android.os.ParcelUuid;
-import android.os.test.TestLooper;
+import android.os.Process;
import android.telephony.SubscriptionInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,7 +57,9 @@
/** Tests for TelephonySubscriptionTracker */
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class VcnGatewayConnectionTest {
+public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
+ private static final int TEST_UID = Process.myUid();
+
private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
private static final int TEST_SIM_SLOT_INDEX = 1;
private static final int TEST_SUBSCRIPTION_ID_1 = 2;
@@ -57,26 +75,67 @@
TEST_SUBID_TO_GROUP_MAP = Collections.unmodifiableMap(subIdToGroupMap);
}
- @NonNull private final Context mContext;
- @NonNull private final TestLooper mTestLooper;
- @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
- @NonNull private final VcnGatewayConnection.Dependencies mDeps;
+ private WifiInfo mWifiInfo;
- public VcnGatewayConnectionTest() {
- mContext = mock(Context.class);
- mTestLooper = new TestLooper();
- mVcnNetworkProvider = mock(VcnNetworkProvider.class);
- mDeps = mock(VcnGatewayConnection.Dependencies.class);
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mWifiInfo = mock(WifiInfo.class);
+ }
+
+ private void verifyBuildNetworkCapabilitiesCommon(int transportType) {
+ final NetworkCapabilities underlyingCaps = new NetworkCapabilities();
+ underlyingCaps.addTransportType(transportType);
+ underlyingCaps.addCapability(NET_CAPABILITY_NOT_METERED);
+ underlyingCaps.addCapability(NET_CAPABILITY_NOT_ROAMING);
+
+ if (transportType == TRANSPORT_WIFI) {
+ underlyingCaps.setTransportInfo(mWifiInfo);
+ underlyingCaps.setOwnerUid(TEST_UID);
+ } else if (transportType == TRANSPORT_CELLULAR) {
+ underlyingCaps.setAdministratorUids(new int[] {TEST_UID});
+ underlyingCaps.setNetworkSpecifier(
+ new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_1));
+ }
+
+ UnderlyingNetworkRecord record =
+ new UnderlyingNetworkRecord(
+ new Network(0), underlyingCaps, new LinkProperties(), false);
+ final NetworkCapabilities vcnCaps =
+ VcnGatewayConnection.buildNetworkCapabilities(
+ VcnGatewayConnectionConfigTest.buildTestConfig(), record);
+
+ assertTrue(vcnCaps.hasTransport(TRANSPORT_CELLULAR));
+ assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+ assertArrayEquals(new int[] {TEST_UID}, vcnCaps.getAdministratorUids());
+ assertTrue(vcnCaps.getTransportInfo() instanceof VcnTransportInfo);
+
+ final VcnTransportInfo info = (VcnTransportInfo) vcnCaps.getTransportInfo();
+ if (transportType == TRANSPORT_WIFI) {
+ assertEquals(mWifiInfo, info.getWifiInfo());
+ } else if (transportType == TRANSPORT_CELLULAR) {
+ assertEquals(TEST_SUBSCRIPTION_ID_1, info.getSubId());
+ }
}
@Test
- public void testBuildNetworkCapabilities() throws Exception {
- final NetworkCapabilities caps =
- VcnGatewayConnection.buildNetworkCapabilities(
- VcnGatewayConnectionConfigTest.buildTestConfig());
+ public void testBuildNetworkCapabilitiesUnderlyingWifi() throws Exception {
+ verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI);
+ }
- for (int exposedCapability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
- assertTrue(caps.hasCapability(exposedCapability));
- }
+ @Test
+ public void testBuildNetworkCapabilitiesUnderlyingCell() throws Exception {
+ verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR);
+ }
+
+ @Test
+ public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
+ final TelephonySubscriptionSnapshot updatedSnapshot =
+ mock(TelephonySubscriptionSnapshot.class);
+ mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot);
+
+ verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot));
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index b4d39bf..df1341c 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -27,7 +27,9 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.IpSecConfig;
import android.net.IpSecManager;
+import android.net.IpSecTransform;
import android.net.IpSecTunnelInterfaceResponse;
import android.net.LinkProperties;
import android.net.Network;
@@ -40,15 +42,21 @@
import android.os.test.TestLooper;
import com.android.server.IpSecService;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
+import java.util.Collections;
import java.util.UUID;
public class VcnGatewayConnectionTestBase {
protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
- protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 1;
+ protected static final int TEST_IPSEC_SPI_VALUE = 0x1234;
+ protected static final int TEST_IPSEC_SPI_RESOURCE_ID = 1;
+ protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2;
+ protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3;
+ protected static final int TEST_SUB_ID = 5;
protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
new UnderlyingNetworkRecord(
@@ -63,6 +71,10 @@
new LinkProperties(),
false /* blocked */);
+ protected static final TelephonySubscriptionSnapshot TEST_SUBSCRIPTION_SNAPSHOT =
+ new TelephonySubscriptionSnapshot(
+ Collections.singletonMap(TEST_SUB_ID, TEST_SUB_GRP), Collections.EMPTY_MAP);
+
@NonNull protected final Context mContext;
@NonNull protected final TestLooper mTestLooper;
@NonNull protected final VcnNetworkProvider mVcnNetworkProvider;
@@ -94,7 +106,7 @@
doReturn(mUnderlyingNetworkTracker)
.when(mDeps)
- .newUnderlyingNetworkTracker(any(), any(), any());
+ .newUnderlyingNetworkTracker(any(), any(), any(), any(), any());
}
@Before
@@ -109,7 +121,13 @@
mMockIkeSession = mock(VcnIkeSession.class);
doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any());
- mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps);
+ mGatewayConnection =
+ new VcnGatewayConnection(
+ mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps);
+ }
+
+ protected IpSecTransform makeDummyIpSecTransform() throws Exception {
+ return new IpSecTransform(mContext, new IpSecConfig());
}
protected IkeSessionCallback getIkeSessionCallback() {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
new file mode 100644
index 0000000..0c1df76
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.vcn;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.NetworkRequest;
+import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Set;
+import java.util.UUID;
+
+public class VcnTest {
+ private static final String PKG_NAME = VcnTest.class.getPackage().getName();
+ private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+ private static final int NETWORK_SCORE = 0;
+ private static final int PROVIDER_ID = 5;
+
+ private Context mContext;
+ private VcnContext mVcnContext;
+ private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+ private VcnNetworkProvider mVcnNetworkProvider;
+ private Vcn.Dependencies mDeps;
+
+ private TestLooper mTestLooper;
+ private VcnConfig mConfig;
+ private Vcn mVcn;
+
+ @Before
+ public void setUp() {
+ mContext = mock(Context.class);
+ mVcnContext = mock(VcnContext.class);
+ mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class);
+ mVcnNetworkProvider = mock(VcnNetworkProvider.class);
+ mDeps = mock(Vcn.Dependencies.class);
+
+ mTestLooper = new TestLooper();
+
+ doReturn(PKG_NAME).when(mContext).getOpPackageName();
+ doReturn(mContext).when(mVcnContext).getContext();
+ doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
+ doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
+
+ // Setup VcnGatewayConnection instance generation
+ doAnswer((invocation) -> {
+ // Mock-within a doAnswer is safe, because it doesn't actually run nested.
+ return mock(VcnGatewayConnection.class);
+ }).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any());
+
+ mConfig =
+ new VcnConfig.Builder(mContext)
+ .addGatewayConnectionConfig(
+ VcnGatewayConnectionConfigTest.buildTestConfig())
+ .build();
+
+ mVcn = new Vcn(mVcnContext, TEST_SUB_GROUP, mConfig, mSubscriptionSnapshot, mDeps);
+ }
+
+ private NetworkRequestListener verifyAndGetRequestListener() {
+ ArgumentCaptor<NetworkRequestListener> mNetworkRequestListenerCaptor =
+ ArgumentCaptor.forClass(NetworkRequestListener.class);
+ verify(mVcnNetworkProvider).registerListener(mNetworkRequestListenerCaptor.capture());
+
+ return mNetworkRequestListenerCaptor.getValue();
+ }
+
+ private NetworkRequest getNetworkRequestWithCapabilities(int[] networkCapabilities) {
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+ for (final int netCapability : networkCapabilities) {
+ builder.addCapability(netCapability);
+ }
+ return builder.build();
+ }
+
+ @Test
+ public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
+ final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+
+ requestListener.onNetworkRequested(
+ getNetworkRequestWithCapabilities(VcnGatewayConnectionConfigTest.EXPOSED_CAPS),
+ NETWORK_SCORE,
+ PROVIDER_ID);
+ mTestLooper.dispatchAll();
+
+ final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections();
+ assertFalse(gatewayConnections.isEmpty());
+
+ final TelephonySubscriptionSnapshot updatedSnapshot =
+ mock(TelephonySubscriptionSnapshot.class);
+
+ mVcn.updateSubscriptionSnapshot(updatedSnapshot);
+ mTestLooper.dispatchAll();
+
+ for (final VcnGatewayConnection gateway : gatewayConnections) {
+ verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot));
+ }
+ }
+}
diff --git a/tools/fonts/update_font_metadata.py b/tools/fonts/update_font_metadata.py
new file mode 100755
index 0000000..c07a98a
--- /dev/null
+++ b/tools/fonts/update_font_metadata.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+import argparse
+
+from fontTools import ttLib
+
+
+def update_font_revision(font, revisionSpec):
+ if revisionSpec.startswith('+'):
+ font['head'].fontRevision += float(revisionSpec[1:])
+ else:
+ font['head'].fontRevision = float(revisionSpec)
+
+
+def main():
+ args_parser = argparse.ArgumentParser(description='Update font file metadata')
+ args_parser.add_argument('--input', help='Input otf/ttf font file.')
+ args_parser.add_argument('--output', help='Output file for updated font file.')
+ args_parser.add_argument('--revision', help='Updated font revision. Use + to update revision based on the current revision')
+ args = args_parser.parse_args()
+
+ font = ttLib.TTFont(args.input)
+ update_font_revision(font, args.revision)
+ font.save(args.output)
+
+if __name__ == "__main__":
+ main()
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index ef26532..c64f4bc 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.AlarmManager;
@@ -948,8 +949,9 @@
* has been set up).
*/
public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
- @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs,
- @Nullable Bundle extraScanningParams) {
+ @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+ @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+ @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);