Merge "[Settings] keys with @TestApi need to have @Readable" into sc-dev
diff --git a/Android.bp b/Android.bp
index 6a47db1..8b15f02 100644
--- a/Android.bp
+++ b/Android.bp
@@ -465,6 +465,7 @@
static_libs: [
"android.net.ipsec.ike.stubs.module_lib",
"framework-appsearch.stubs.module_lib",
+ "framework-connectivity.stubs.module_lib",
"framework-graphics.stubs.module_lib",
"framework-media.stubs.module_lib",
"framework-mediaprovider.stubs.module_lib",
@@ -487,6 +488,7 @@
"android.net.ipsec.ike.impl",
"framework-minus-apex",
"framework-appsearch.impl",
+ "framework-connectivity.impl",
"framework-graphics.impl",
"framework-mediaprovider.impl",
"framework-permission.impl",
@@ -643,14 +645,17 @@
defaults: ["framework-aidl-export-defaults"],
srcs: [
":framework-non-updatable-sources",
- ":framework-connectivity-sources",
"core/java/**/*.logtags",
],
// See comment on framework-atb-backward-compatibility module below
exclude_srcs: ["core/java/android/content/pm/AndroidTestBaseUpdater.java"],
aidl: {
generate_get_transaction_name: true,
- local_include_dirs: ["media/aidl"],
+ local_include_dirs: [
+ "media/aidl",
+ // TODO: move to include_dirs when migrated to packages/modules
+ "packages/Connectivity/framework/aidl-export",
+ ],
include_dirs: ["frameworks/av/aidl"],
},
dxflags: [
@@ -703,8 +708,6 @@
apex_available: ["//apex_available:platform"],
visibility: [
"//frameworks/base",
- // TODO: remove when framework-connectivity can build against API
- "//frameworks/base/packages/Connectivity/framework",
// TODO(b/147128803) remove the below lines
"//frameworks/base/apex/appsearch/framework",
"//frameworks/base/apex/blobstore/framework",
@@ -743,6 +746,7 @@
static_libs: [
"app-compat-annotations",
"framework-minus-apex",
+ "framework-connectivity.impl", // TODO(b/182859030): should be removed
"framework-appsearch.impl", // TODO(b/146218515): should be removed
"framework-updatable-stubs-module_libs_api",
],
@@ -1461,7 +1465,7 @@
],
libs: [
"framework-annotations-lib",
- "framework-connectivity",
+ "framework-connectivity.stubs.module_lib",
"unsupportedappusage",
],
visibility: [
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index 47cf17c..5acfe6a 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -26,10 +26,8 @@
}
public static final class AppSearchManager.SearchContext.Builder {
- ctor @Deprecated public AppSearchManager.SearchContext.Builder();
ctor public AppSearchManager.SearchContext.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchManager.SearchContext build();
- method @Deprecated @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
}
public final class AppSearchResult<ValueType> {
@@ -53,7 +51,6 @@
public final class AppSearchSchema {
method @NonNull public java.util.List<android.app.appsearch.AppSearchSchema.PropertyConfig> getProperties();
method @NonNull public String getSchemaType();
- method @Deprecated @IntRange(from=0) public int getVersion();
}
public static final class AppSearchSchema.BooleanPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
@@ -69,7 +66,6 @@
ctor public AppSearchSchema.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchSchema.Builder addProperty(@NonNull android.app.appsearch.AppSearchSchema.PropertyConfig);
method @NonNull public android.app.appsearch.AppSearchSchema build();
- method @Deprecated @NonNull public android.app.appsearch.AppSearchSchema.Builder setVersion(@IntRange(from=0) int);
}
public static final class AppSearchSchema.BytesPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
@@ -149,7 +145,6 @@
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 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);
- method @Deprecated 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>>);
method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
}
@@ -181,15 +176,12 @@
method public int getScore();
method public long getTtlMillis();
method @NonNull public String getUri();
- field @Deprecated public static final String DEFAULT_NAMESPACE = "";
}
public static class GenericDocument.Builder<BuilderType extends android.app.appsearch.GenericDocument.Builder> {
- ctor @Deprecated public GenericDocument.Builder(@NonNull String, @NonNull String);
ctor public GenericDocument.Builder(@NonNull String, @NonNull String, @NonNull String);
method @NonNull public android.app.appsearch.GenericDocument build();
method @NonNull public BuilderType setCreationTimestampMillis(long);
- method @Deprecated @NonNull public BuilderType setNamespace(@NonNull String);
method @NonNull public BuilderType setPropertyBoolean(@NonNull String, @NonNull boolean...);
method @NonNull public BuilderType setPropertyBytes(@NonNull String, @NonNull byte[]...);
method @NonNull public BuilderType setPropertyDocument(@NonNull String, @NonNull android.app.appsearch.GenericDocument...);
@@ -208,16 +200,14 @@
}
public static final class GetByUriRequest.Builder {
- ctor @Deprecated public GetByUriRequest.Builder();
ctor public GetByUriRequest.Builder(@NonNull String);
method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.GetByUriRequest build();
- method @Deprecated @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String);
}
- public class GetSchemaResponse extends java.util.HashSet<android.app.appsearch.AppSearchSchema> {
+ public class GetSchemaResponse {
method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas();
method @IntRange(from=0) public int getVersion();
}
@@ -265,12 +255,10 @@
}
public static final class RemoveByUriRequest.Builder {
- ctor @Deprecated public RemoveByUriRequest.Builder();
ctor public RemoveByUriRequest.Builder(@NonNull String);
method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.RemoveByUriRequest build();
- method @Deprecated @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String);
}
public final class ReportSystemUsageRequest {
@@ -295,17 +283,14 @@
}
public static final class ReportUsageRequest.Builder {
- ctor @Deprecated public ReportUsageRequest.Builder();
ctor public ReportUsageRequest.Builder(@NonNull String);
method @NonNull public android.app.appsearch.ReportUsageRequest build();
- method @Deprecated @NonNull public android.app.appsearch.ReportUsageRequest.Builder setNamespace(@NonNull String);
method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setUri(@NonNull String);
method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setUsageTimeMillis(long);
}
public final class SearchResult {
method @NonNull public String getDatabaseName();
- method @Deprecated @NonNull public android.app.appsearch.GenericDocument getDocument();
method @NonNull public android.app.appsearch.GenericDocument getGenericDocument();
method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches();
method @NonNull public String getPackageName();
@@ -322,12 +307,10 @@
public static final class SearchResult.MatchInfo {
method @NonNull public CharSequence getExactMatch();
- method @Deprecated @NonNull public android.app.appsearch.SearchResult.MatchRange getExactMatchPosition();
method @NonNull public android.app.appsearch.SearchResult.MatchRange getExactMatchRange();
method @NonNull public String getFullText();
method @NonNull public String getPropertyPath();
method @NonNull public CharSequence getSnippet();
- method @Deprecated @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetPosition();
method @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetRange();
}
@@ -405,7 +388,6 @@
method @NonNull public java.util.Map<java.lang.String,android.app.appsearch.Migrator> getMigrators();
method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas();
method @NonNull public java.util.Set<java.lang.String> getSchemasNotDisplayedBySystem();
- method @Deprecated @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi();
method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages();
method @IntRange(from=1) public int getVersion();
method public boolean isForceOverride();
@@ -421,7 +403,6 @@
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrators(@NonNull java.util.Map<java.lang.String,android.app.appsearch.Migrator>);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeDisplayedBySystem(@NonNull String, boolean);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier);
- method @Deprecated @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setVersion(@IntRange(from=1) int);
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 0c6b86b..9776827 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -147,22 +147,10 @@
/** Builder for {@link SearchContext} objects. */
public static final class Builder {
- private String mDatabaseName;
+ private final String mDatabaseName;
private boolean mBuilt = false;
/**
- * TODO(b/181887768): This method exists only for dogfooder transition and must be
- * removed.
- *
- * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
- * method exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- public Builder() {
- mDatabaseName = "";
- }
-
- /**
* Creates a new {@link SearchContext.Builder}.
*
* <p>{@link AppSearchSession} will create or open a database under the given name.
@@ -182,37 +170,6 @@
mDatabaseName = databaseName;
}
- /**
- * Sets the name of the database associated with {@link AppSearchSession}.
- *
- * <p>{@link AppSearchSession} will create or open a database under the given name.
- *
- * <p>Databases with different names are fully separate with distinct types, namespaces,
- * and data.
- *
- * <p>Database name cannot contain {@code '/'}.
- *
- * <p>If not specified, defaults to the empty string.
- *
- * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
- * removed.
- *
- * @param databaseName The name of the database.
- * @throws IllegalArgumentException if the databaseName contains {@code '/'}.
- * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
- * method exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- @NonNull
- public Builder setDatabaseName(@NonNull String databaseName) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- Objects.requireNonNull(databaseName);
- Preconditions.checkArgument(
- !databaseName.contains("/"), "Database name cannot contain '/'");
- mDatabaseName = databaseName;
- return this;
- }
-
/** Builds a {@link SearchContext} instance. */
@NonNull
public SearchContext build() {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
new file mode 100644
index 0000000..e585d91
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
@@ -0,0 +1,256 @@
+/*
+ * 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.appsearch;
+
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * The helper class for {@link AppSearchSchema} migration.
+ *
+ * <p>It will query and migrate {@link GenericDocument} in given type to a new version.
+ * @hide
+ */
+public class AppSearchMigrationHelper implements Closeable {
+ private final IAppSearchManager mService;
+ private final String mPackageName;
+ private final String mDatabaseName;
+ private final int mUserId;
+ private final File mMigratedFile;
+ private final Map<String, Integer> mCurrentVersionMap;
+ private final Map<String, Integer> mFinalVersionMap;
+ private boolean mAreDocumentsMigrated = false;
+
+ AppSearchMigrationHelper(@NonNull IAppSearchManager service,
+ @UserIdInt int userId,
+ @NonNull Map<String, Integer> currentVersionMap,
+ @NonNull Map<String, Integer> finalVersionMap,
+ @NonNull String packageName,
+ @NonNull String databaseName) throws IOException {
+ mService = Objects.requireNonNull(service);
+ mCurrentVersionMap = Objects.requireNonNull(currentVersionMap);
+ mFinalVersionMap = Objects.requireNonNull(finalVersionMap);
+ mPackageName = Objects.requireNonNull(packageName);
+ mDatabaseName = Objects.requireNonNull(databaseName);
+ mUserId = userId;
+ mMigratedFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null);
+ }
+
+ /**
+ * Queries all documents that need to be migrated to a different version and transform
+ * documents to that version by passing them to the provided {@link Migrator}.
+ *
+ * <p>The method will be executed on the executor provided to
+ * {@link AppSearchSession#setSchema}.
+ *
+ * @param schemaType The schema type that needs to be updated and whose {@link GenericDocument}
+ * need to be migrated.
+ * @param migrator The {@link Migrator} that will upgrade or downgrade a {@link
+ * GenericDocument} to new version.
+ */
+ @WorkerThread
+ public void queryAndTransform(@NonNull String schemaType, @NonNull Migrator migrator)
+ throws IOException, AppSearchException, InterruptedException, ExecutionException {
+ File queryFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null);
+ try (ParcelFileDescriptor fileDescriptor =
+ ParcelFileDescriptor.open(queryFile, MODE_WRITE_ONLY)) {
+ AndroidFuture<AppSearchResult<Void>> androidFuture = new AndroidFuture<>();
+ mService.writeQueryResultsToFile(mPackageName, mDatabaseName,
+ fileDescriptor,
+ /*queryExpression=*/ "",
+ new SearchSpec.Builder()
+ .addFilterSchemas(schemaType)
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build().getBundle(),
+ mUserId,
+ new IAppSearchResultCallback.Stub() {
+ @Override
+ public void onResult(AppSearchResult result) throws RemoteException {
+ androidFuture.complete(result);
+ }
+ });
+ AppSearchResult<Void> result = androidFuture.get();
+ if (!result.isSuccess()) {
+ throw new AppSearchException(result.getResultCode(), result.getErrorMessage());
+ }
+ readAndTransform(queryFile, migrator);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ queryFile.delete();
+ }
+ }
+
+ /**
+ * Puts all {@link GenericDocument} migrated from the previous call to
+ * {@link #queryAndTransform} into AppSearch.
+ *
+ * <p> This method should be only called once.
+ *
+ * @param responseBuilder a SetSchemaResponse builder whose result will be returned by this
+ * function with any
+ * {@link android.app.appsearch.SetSchemaResponse.MigrationFailure}
+ * added in.
+ * @return the {@link SetSchemaResponse} for {@link AppSearchSession#setSchema} call.
+ */
+ @NonNull
+ AppSearchResult<SetSchemaResponse> putMigratedDocuments(
+ @NonNull SetSchemaResponse.Builder responseBuilder) {
+ if (!mAreDocumentsMigrated) {
+ return AppSearchResult.newSuccessfulResult(responseBuilder.build());
+ }
+ try (ParcelFileDescriptor fileDescriptor =
+ ParcelFileDescriptor.open(mMigratedFile, MODE_READ_ONLY)) {
+ AndroidFuture<AppSearchResult<List<Bundle>>> androidFuture = new AndroidFuture<>();
+ mService.putDocumentsFromFile(mPackageName, mDatabaseName, fileDescriptor, mUserId,
+ new IAppSearchResultCallback.Stub() {
+ @Override
+ public void onResult(AppSearchResult result) throws RemoteException {
+ androidFuture.complete(result);
+ }
+ });
+ AppSearchResult<List<Bundle>> result = androidFuture.get();
+ if (!result.isSuccess()) {
+ return AppSearchResult.newFailedResult(result);
+ }
+ List<Bundle> migratedFailureBundles = result.getResultValue();
+ for (int i = 0; i < migratedFailureBundles.size(); i++) {
+ responseBuilder.addMigrationFailure(
+ new SetSchemaResponse.MigrationFailure(migratedFailureBundles.get(i)));
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (Throwable t) {
+ return AppSearchResult.throwableToFailedResult(t);
+ } finally {
+ mMigratedFile.delete();
+ }
+ return AppSearchResult.newSuccessfulResult(responseBuilder.build());
+ }
+
+ /**
+ * Reads all saved {@link GenericDocument}s from the given {@link File}.
+ *
+ * <p>Transforms those {@link GenericDocument}s to the final version.
+ *
+ * <p>Save migrated {@link GenericDocument}s to the {@link #mMigratedFile}.
+ */
+ private void readAndTransform(@NonNull File file, @NonNull Migrator migrator)
+ throws IOException {
+ try (DataInputStream inputStream = new DataInputStream(new FileInputStream(file));
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(
+ mMigratedFile, /*append=*/ true))) {
+ GenericDocument document;
+ while (true) {
+ try {
+ document = readDocumentFromInputStream(inputStream);
+ } catch (EOFException e) {
+ break;
+ // Nothing wrong. We just finished reading.
+ }
+
+ int currentVersion = mCurrentVersionMap.get(document.getSchemaType());
+ int finalVersion = mFinalVersionMap.get(document.getSchemaType());
+
+ GenericDocument newDocument;
+ if (currentVersion < finalVersion) {
+ newDocument = migrator.onUpgrade(currentVersion, finalVersion, document);
+ } else {
+ // currentVersion == finalVersion case won't trigger migration and get here.
+ newDocument = migrator.onDowngrade(currentVersion, finalVersion, document);
+ }
+ writeBundleToOutputStream(outputStream, newDocument.getBundle());
+ }
+ mAreDocumentsMigrated = true;
+ }
+ }
+
+ /**
+ * Reads the {@link Bundle} of a {@link GenericDocument} from given {@link DataInputStream}.
+ *
+ * @param inputStream The inputStream to read from
+ *
+ * @throws IOException on read failure.
+ * @throws EOFException if {@link java.io.InputStream} reaches the end.
+ */
+ @NonNull
+ public static GenericDocument readDocumentFromInputStream(
+ @NonNull DataInputStream inputStream) throws IOException {
+ int length = inputStream.readInt();
+ if (length == 0) {
+ throw new EOFException();
+ }
+ byte[] serializedMessage = new byte[length];
+ inputStream.read(serializedMessage);
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
+ parcel.setDataPosition(0);
+ Bundle bundle = parcel.readBundle();
+ return new GenericDocument(bundle);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ /**
+ * Serializes a {@link Bundle} and writes into the given {@link DataOutputStream}.
+ */
+ public static void writeBundleToOutputStream(
+ @NonNull DataOutputStream outputStream, @NonNull Bundle bundle)
+ throws IOException {
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeBundle(bundle);
+ byte[] serializedMessage = parcel.marshall();
+ outputStream.writeInt(serializedMessage.length);
+ outputStream.write(serializedMessage);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ mMigratedFile.delete();
+ }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index bf733ed..0f6468a 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -19,6 +19,8 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.app.appsearch.util.SchemaMigrationUtil;
import android.os.Bundle;
import android.os.ParcelableException;
import android.os.RemoteException;
@@ -26,14 +28,17 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import java.io.Closeable;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -55,7 +60,6 @@
private boolean mIsMutated = false;
private boolean mIsClosed = false;
-
/**
* Creates a search session for the client, defined by the {@code userId} and
* {@code packageName}.
@@ -104,18 +108,6 @@
}
/**
- * TODO(b/181887768): This method exists only for dogfooder transition and must be removed.
- * @deprecated This method exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- public void setSchema(
- @NonNull SetSchemaRequest request,
- @NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
- setSchema(request, callbackExecutor, callbackExecutor, callback);
- }
-
- /**
* Sets the schema that represents the organizational structure of data within the AppSearch
* database.
*
@@ -157,26 +149,65 @@
}
schemasPackageAccessibleBundles.put(entry.getKey(), packageIdentifierBundles);
}
+
+ // No need to trigger migration if user never set migrator
+ if (request.getMigrators().isEmpty()) {
+ setSchemaNoMigrations(
+ request,
+ schemaBundles,
+ schemasPackageAccessibleBundles,
+ callbackExecutor,
+ callback);
+ return;
+ }
+
try {
+ // Migration process
+ // 1. Generate the current and the final version map.
+ // TODO(b/182855402) Release binder thread and move the heavy work into worker thread.
+ AndroidFuture<AppSearchResult<GetSchemaResponse>> future = new AndroidFuture<>();
+ getSchema(callbackExecutor, future::complete);
+ AppSearchResult<GetSchemaResponse> getSchemaResult = future.get();
+ if (!getSchemaResult.isSuccess()) {
+ callback.accept(AppSearchResult.newFailedResult(getSchemaResult));
+ return;
+ }
+ GetSchemaResponse getSchemaResponse = getSchemaResult.getResultValue();
+ Set<AppSearchSchema> currentSchemas = getSchemaResponse.getSchemas();
+ Map<String, Integer> currentVersionMap =
+ SchemaMigrationUtil.buildVersionMap(currentSchemas,
+ getSchemaResponse.getVersion());
+ Map<String, Integer> finalVersionMap =
+ SchemaMigrationUtil.buildVersionMap(request.getSchemas(), request.getVersion());
+
+ // 2. SetSchema with forceOverride=false, to retrieve the list of incompatible/deleted
+ // types.
mService.setSchema(
mPackageName,
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
schemasPackageAccessibleBundles,
- request.isForceOverride(),
+ /*forceOverride=*/ false,
mUserId,
request.getVersion(),
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
callbackExecutor.execute(() -> {
if (result.isSuccess()) {
- callback.accept(
- // TODO(b/177266929) implement Migration in platform.
- // TODO(b/183177268): once migration is implemented, run
- // it on workExecutor.
- AppSearchResult.newSuccessfulResult(
- new SetSchemaResponse.Builder().build()));
+ // TODO(b/183177268): once migration is implemented, run
+ // it on workExecutor.
+ try {
+ Bundle bundle = (Bundle) result.getResultValue();
+ SetSchemaResponse setSchemaResponse =
+ new SetSchemaResponse(bundle);
+ setSchemaMigration(
+ request, setSchemaResponse, schemaBundles,
+ schemasPackageAccessibleBundles, currentVersionMap,
+ finalVersionMap, callback);
+ } catch (Throwable t) {
+ callback.accept(AppSearchResult.throwableToFailedResult(t));
+ }
} else {
callback.accept(result);
}
@@ -186,6 +217,8 @@
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (Throwable t) {
+ callback.accept(AppSearchResult.throwableToFailedResult(t));
}
}
@@ -627,4 +660,159 @@
}
}
}
+
+ /**
+ * Set schema to Icing for no-migration scenario.
+ *
+ * <p>We only need one time {@link #setSchema} call for no-migration scenario by using the
+ * forceoverride in the request.
+ */
+ private void setSchemaNoMigrations(@NonNull SetSchemaRequest request,
+ @NonNull List<Bundle> schemaBundles,
+ @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
+ try {
+ mService.setSchema(
+ mPackageName,
+ mDatabaseName,
+ schemaBundles,
+ new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
+ schemasPackageAccessibleBundles,
+ request.isForceOverride(),
+ mUserId,
+ request.getVersion(),
+ new IAppSearchResultCallback.Stub() {
+ public void onResult(AppSearchResult result) {
+ executor.execute(() -> {
+ if (result.isSuccess()) {
+ try {
+ SetSchemaResponse setSchemaResponse =
+ new SetSchemaResponse(
+ (Bundle) result.getResultValue());
+ if (!request.isForceOverride()) {
+ // Throw exception if there is any deleted types or
+ // incompatible types. That's the only case we swallowed
+ // in the AppSearchImpl#setSchema().
+ checkDeletedAndIncompatible(
+ setSchemaResponse.getDeletedTypes(),
+ setSchemaResponse.getIncompatibleTypes());
+ }
+ callback.accept(AppSearchResult
+ .newSuccessfulResult(setSchemaResponse));
+ } catch (Throwable t) {
+ callback.accept(AppSearchResult.throwableToFailedResult(t));
+ }
+ } else {
+ callback.accept(result);
+ }
+ });
+ }
+ });
+ mIsMutated = true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set schema to Icing for migration scenario.
+ *
+ * <p>First time {@link #setSchema} call with forceOverride is false gives us all incompatible
+ * changes. After trigger migrations, the second time call {@link #setSchema} will actually
+ * apply the changes.
+ *
+ * @param setSchemaResponse the result of the first setSchema call with forceOverride=false.
+ */
+ private void setSchemaMigration(@NonNull SetSchemaRequest request,
+ @NonNull SetSchemaResponse setSchemaResponse,
+ @NonNull List<Bundle> schemaBundles,
+ @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles,
+ @NonNull Map<String, Integer> currentVersionMap, Map<String, Integer> finalVersionMap,
+ @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback)
+ throws AppSearchException, IOException, RemoteException, ExecutionException,
+ InterruptedException {
+ // 1. If forceOverride is false, check that all incompatible types will be migrated.
+ // If some aren't we must throw an error, rather than proceeding and deleting those
+ // types.
+ if (!request.isForceOverride()) {
+ Set<String> unmigratedTypes = SchemaMigrationUtil.getUnmigratedIncompatibleTypes(
+ setSchemaResponse.getIncompatibleTypes(),
+ request.getMigrators(),
+ currentVersionMap,
+ finalVersionMap);
+ // check if there are any unmigrated types or deleted types. If there are, we will throw
+ // an exception.
+ // Since the force override is false, the schema will not have been set if there are any
+ // incompatible or deleted types.
+ checkDeletedAndIncompatible(setSchemaResponse.getDeletedTypes(),
+ unmigratedTypes);
+ }
+
+ try (AppSearchMigrationHelper migrationHelper =
+ new AppSearchMigrationHelper(mService, mUserId, currentVersionMap,
+ finalVersionMap, mPackageName, mDatabaseName)) {
+ Map<String, Migrator> migratorMap = request.getMigrators();
+
+ // 2. Trigger migration for all migrators.
+ // TODO(b/177266929) trigger migration for all types together rather than separately.
+ Set<String> migratedTypes = new ArraySet<>();
+ for (Map.Entry<String, Migrator> entry : migratorMap.entrySet()) {
+ String schemaType = entry.getKey();
+ Migrator migrator = entry.getValue();
+ if (SchemaMigrationUtil.shouldTriggerMigration(
+ schemaType, migrator, currentVersionMap, finalVersionMap)) {
+ migrationHelper.queryAndTransform(schemaType, migrator);
+ migratedTypes.add(schemaType);
+ }
+ }
+
+ // 3. SetSchema a second time with forceOverride=true if the first attempted failed.
+ if (!setSchemaResponse.getIncompatibleTypes().isEmpty()
+ || !setSchemaResponse.getDeletedTypes().isEmpty()) {
+ AndroidFuture<AppSearchResult<SetSchemaResponse>> future = new AndroidFuture<>();
+ // only trigger second setSchema() call if the first one is fail.
+ mService.setSchema(
+ mPackageName,
+ mDatabaseName,
+ schemaBundles,
+ new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
+ schemasPackageAccessibleBundles,
+ /*forceOverride=*/ true,
+ mUserId,
+ request.getVersion(),
+ new IAppSearchResultCallback.Stub() {
+ @Override
+ public void onResult(AppSearchResult result) throws RemoteException {
+ future.complete(result);
+ }
+ });
+ AppSearchResult<SetSchemaResponse> secondSetSchemaResult = future.get();
+ if (!secondSetSchemaResult.isSuccess()) {
+ // we failed to set the schema in second time with force override = true, which
+ // is an impossible case. Since we only swallow the incompatible error in the
+ // first setSchema call, all other errors will be thrown at the first time.
+ callback.accept(secondSetSchemaResult);
+ return;
+ }
+ }
+
+ SetSchemaResponse.Builder responseBuilder = setSchemaResponse.toBuilder()
+ .addMigratedTypes(migratedTypes);
+ callback.accept(migrationHelper.putMigratedDocuments(responseBuilder));
+ }
+ }
+
+ /** Checks the setSchema() call won't delete any types or has incompatible types. */
+ //TODO(b/177266929) move this method to util
+ private void checkDeletedAndIncompatible(Set<String> deletedTypes,
+ Set<String> incompatibleTypes)
+ throws AppSearchException {
+ if (!deletedTypes.isEmpty() || !incompatibleTypes.isEmpty()) {
+ String newMessage = "Schema is incompatible."
+ + "\n Deleted types: " + deletedTypes
+ + "\n Incompatible types: " + incompatibleTypes;
+ throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage);
+ }
+ }
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index d436488..48c397f 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -21,6 +21,7 @@
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchResultCallback;
+import android.os.ParcelFileDescriptor;
import com.android.internal.infra.AndroidFuture;
parcelable SearchResults;
@@ -41,7 +42,8 @@
* incompatible documents will be deleted.
* @param userId Id of the calling user
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
- * {@link AppSearchResult}<{@link Void}>.
+ * {@link AppSearchResult}<{@link Bundle}>, where the value are
+ * {@link SetSchemaResponse} bundle.
*/
void setSchema(
in String packageName,
@@ -189,6 +191,47 @@
void invalidateNextPageToken(in long nextPageToken, in int userId);
/**
+ * Searches a document based on a given specifications.
+ *
+ * <p>Documents will be save to the given ParcelFileDescriptor
+ *
+ * @param packageName The name of the package to query over.
+ * @param databaseName The databaseName this query for.
+ * @param fileDescriptor The ParcelFileDescriptor where documents should be written to.
+ * @param queryExpression String to search for.
+ * @param searchSpecBundle SearchSpec bundle.
+ * @param userId Id of the calling user.
+ * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
+ * {@link AppSearchResult}<{@code null}>.
+ */
+ void writeQueryResultsToFile(
+ in String packageName,
+ in String databaseName,
+ in ParcelFileDescriptor fileDescriptor,
+ in String queryExpression,
+ in Bundle searchSpecBundle,
+ in int userId,
+ in IAppSearchResultCallback callback);
+
+ /**
+ * Inserts documents from the given file into the index.
+ *
+ * @param packageName The name of the package that owns this document.
+ * @param databaseName The name of the database where this document lives.
+ * @param fileDescriptor The ParcelFileDescriptor where documents should be read from.
+ * @param userId Id of the calling user.
+ * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
+ * {@link AppSearchResult}<{@link List}<{@link Bundle}>>, where the value are
+ * MigrationFailure bundles.
+ */
+ void putDocumentsFromFile(
+ in String packageName,
+ in String databaseName,
+ in ParcelFileDescriptor fileDescriptor,
+ in int userId,
+ in IAppSearchResultCallback callback);
+
+ /**
* Reports usage of a particular document by URI and namespace.
*
* <p>A usage report represents an event in which a user interacted with or viewed a document.
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 79b7b75..a8048dc 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -17,7 +17,6 @@
package android.app.appsearch;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.exceptions.IllegalSchemaException;
@@ -77,12 +76,6 @@
return mBundle.getString(SCHEMA_TYPE_FIELD, "");
}
- /** @deprecated Use {@link GetSchemaResponse#getVersion()} instead. */
- @Deprecated
- public @IntRange(from = 0) int getVersion() {
- return 0;
- }
-
/**
* Returns the list of {@link PropertyConfig}s that are part of this schema.
*
@@ -150,17 +143,6 @@
}
/**
- * @deprecated TODO(b/181887768): This method is a no-op and only exists for dogfooder
- * transition.
- */
- @Deprecated
- @NonNull
- public AppSearchSchema.Builder setVersion(@IntRange(from = 0) int version) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- return this;
- }
-
- /**
* Constructs a new {@link AppSearchSchema} from the contents of this builder.
*
* <p>After calling this method, the builder must no longer be 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 4ce95ea..8c9d950 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -46,15 +46,6 @@
public class GenericDocument {
private static final String TAG = "AppSearchGenericDocumen";
- /**
- * The default empty namespace.
- *
- * <p>TODO(b/181887768): This exists only for dogfooder transition and must be removed.
- *
- * @deprecated This exists only for dogfooder transition and must be removed.
- */
- @Deprecated public static final String DEFAULT_NAMESPACE = "";
-
/** The maximum number of elements in a repeatable field. */
private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
@@ -585,41 +576,6 @@
*
* <p>Once {@link #build} is called, the instance can no longer be used.
*
- * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
- * removed.
- *
- * @param uri the URI to set for the {@link GenericDocument}.
- * @param schemaType the {@link AppSearchSchema} type of the {@link GenericDocument}. The
- * provided {@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#put}. Otherwise, the document will be rejected by
- * {@link AppSearchSession#put} with result code {@link
- * AppSearchResult#RESULT_NOT_FOUND}.
- * @deprecated Please supply the namespace in {@link #Builder(String, String, String)}
- * instead. This method exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- @SuppressWarnings("unchecked")
- public Builder(@NonNull String uri, @NonNull String schemaType) {
- Preconditions.checkNotNull(uri);
- Preconditions.checkNotNull(schemaType);
- mBuilderTypeInstance = (BuilderType) this;
- mBundle.putString(GenericDocument.URI_FIELD, uri);
- mBundle.putString(GenericDocument.SCHEMA_TYPE_FIELD, schemaType);
- mBundle.putString(GenericDocument.NAMESPACE_FIELD, DEFAULT_NAMESPACE);
- // Set current timestamp for creation timestamp by default.
- mBundle.putLong(
- GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
- mBundle.putLong(GenericDocument.TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
- mBundle.putInt(GenericDocument.SCORE_FIELD, DEFAULT_SCORE);
- mBundle.putBundle(PROPERTIES_FIELD, mProperties);
- }
-
- /**
- * Creates a new {@link GenericDocument.Builder}.
- *
- * <p>Once {@link #build} is called, the instance can no longer be used.
- *
* <p>URIs are unique within a namespace.
*
* <p>The number of namespaces per app should be kept small for efficiency reasons.
@@ -651,29 +607,6 @@
}
/**
- * Sets the app-defined namespace this document resides in. No special values are reserved
- * or understood by the infrastructure.
- *
- * <p>URIs are unique within a namespace.
- *
- * <p>The number of namespaces per app should be kept small for efficiency reasons.
- *
- * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
- * removed.
- *
- * @throws IllegalStateException if the builder has already been used.
- * @deprecated Please supply the namespace in {@link #Builder(String, String, String)}
- * instead. This method exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- @NonNull
- public BuilderType setNamespace(@NonNull String namespace) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
- return mBuilderTypeInstance;
- }
-
- /**
* Sets the score of the {@link GenericDocument}.
*
* <p>The score is a query-independent measure of the document's quality, relative to other
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 6881a27..1719e14 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -107,49 +107,17 @@
* <p>Once {@link #build} is called, the instance can no longer be used.
*/
public static final class Builder {
- private String mNamespace;
+ private final String mNamespace;
private final Set<String> mUris = new ArraySet<>();
private final Map<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>();
private boolean mBuilt = false;
- /**
- * TODO(b/181887768): This method exists only for dogfooder transition and must be removed.
- *
- * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method
- * exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- public Builder() {
- mNamespace = GenericDocument.DEFAULT_NAMESPACE;
- }
-
/** Creates a {@link GetByUriRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
mNamespace = Preconditions.checkNotNull(namespace);
}
/**
- * Sets the namespace to retrieve documents for.
- *
- * <p>If this is not called, the namespace defaults to an empty string.
- *
- * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
- * removed.
- *
- * @throws IllegalStateException if the builder has already been used.
- * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method
- * exists only for dogfooder transition and must
- */
- @Deprecated
- @NonNull
- public Builder setNamespace(@NonNull String namespace) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(namespace);
- mNamespace = namespace;
- return this;
- }
-
- /**
* Adds one or more URIs to the request.
*
* @throws IllegalStateException if the builder has already been used.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
index 3e69367..1f56ef3 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
@@ -24,30 +24,17 @@
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.Set;
/** The response class of {@link AppSearchSession#getSchema} */
-// TODO(b/181887768) extends only for dogfooder transition. */
-public class GetSchemaResponse extends HashSet<AppSearchSchema> {
+public class GetSchemaResponse {
private static final String VERSION_FIELD = "version";
private static final String SCHEMAS_FIELD = "schemas";
private final Bundle mBundle;
- // TODO(b/181887768) Remove this method once this class no longer extends HashSet. */
- private static Set<AppSearchSchema> getSchemasFromBundle(Bundle bundle) {
- ArrayList<Bundle> schemaBundles = bundle.getParcelableArrayList(SCHEMAS_FIELD);
- Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
- for (int i = 0; i < schemaBundles.size(); i++) {
- schemas.add(new AppSearchSchema(schemaBundles.get(i)));
- }
- return schemas;
- }
-
GetSchemaResponse(@NonNull Bundle bundle) {
- super(getSchemasFromBundle(Preconditions.checkNotNull(bundle)));
- mBundle = bundle;
+ mBundle = Preconditions.checkNotNull(bundle);
}
/**
@@ -72,10 +59,17 @@
/**
* Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}.
+ *
+ * <p>It is inefficient to call this method repeatedly.
*/
@NonNull
public Set<AppSearchSchema> getSchemas() {
- return this;
+ ArrayList<Bundle> schemaBundles = mBundle.getParcelableArrayList(SCHEMAS_FIELD);
+ Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
+ for (int i = 0; i < schemaBundles.size(); i++) {
+ schemas.add(new AppSearchSchema(schemaBundles.get(i)));
+ }
+ return schemas;
}
/** Builder for {@link GetSchemaResponse} objects. */
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 455cf3a..8da68c0 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -59,48 +59,16 @@
* <p>Once {@link #build} is called, the instance can no longer be used.
*/
public static final class Builder {
- private String mNamespace;
+ private final String mNamespace;
private final Set<String> mUris = new ArraySet<>();
private boolean mBuilt = false;
- /**
- * TODO(b/181887768): This method exists only for dogfooder transition and must be removed.
- *
- * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method
- * exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- public Builder() {
- mNamespace = GenericDocument.DEFAULT_NAMESPACE;
- }
-
/** Creates a {@link RemoveByUriRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
mNamespace = Preconditions.checkNotNull(namespace);
}
/**
- * Sets the namespace to remove documents for.
- *
- * <p>If this is not set, it defaults to an empty string.
- *
- * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
- * removed.
- *
- * @throws IllegalStateException if the builder has already been used.
- * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method
- * exists only for dogfooder transition and must
- */
- @Deprecated
- @NonNull
- public Builder setNamespace(@NonNull String namespace) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(namespace);
- mNamespace = namespace;
- return this;
- }
-
- /**
* Adds one or more URIs to the request.
*
* @throws IllegalStateException if the builder has already been used.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
index 2cd08c6..646e73c 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
@@ -62,49 +62,17 @@
/** Builder for {@link ReportUsageRequest} objects. */
public static final class Builder {
- private String mNamespace;
+ private final String mNamespace;
private String mUri;
private Long mUsageTimeMillis;
private boolean mBuilt = false;
- /**
- * TODO(b/181887768): This method exists only for dogfooder transition and must be removed.
- *
- * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method
- * exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- public Builder() {
- mNamespace = GenericDocument.DEFAULT_NAMESPACE;
- }
-
/** Creates a {@link ReportUsageRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
mNamespace = Preconditions.checkNotNull(namespace);
}
/**
- * Sets which namespace the document being used belongs to.
- *
- * <p>If this is not set, it defaults to an empty string.
- *
- * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
- * removed.
- *
- * @throws IllegalStateException if the builder has already been used
- * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method
- * exists only for dogfooder transition and must
- */
- @Deprecated
- @NonNull
- public ReportUsageRequest.Builder setNamespace(@NonNull String namespace) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(namespace);
- mNamespace = namespace;
- return this;
- }
-
- /**
* Sets the URI of the document being used.
*
* <p>This field is required.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
index e66056f..55a228d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -68,13 +68,6 @@
return mBundle;
}
- /** @deprecated TODO(b/181887768): This method exists only for dogfooder transition. */
- @NonNull
- @Deprecated
- public GenericDocument getDocument() {
- return getGenericDocument();
- }
-
/**
* Contains the matching {@link GenericDocument}.
*
@@ -142,17 +135,18 @@
* <ul>
* <li>{@link SearchSpec#RANKING_STRATEGY_NONE} - this value will be 0
* <li>{@link SearchSpec#RANKING_STRATEGY_DOCUMENT_SCORE} - the value returned by calling
- * {@link GenericDocument#getScore()} on the document returned by {@link #getDocument()}
+ * {@link GenericDocument#getScore()} on the document returned by {@link
+ * #getGenericDocument()}
* <li>{@link SearchSpec#RANKING_STRATEGY_CREATION_TIMESTAMP} - the value returned by calling
* {@link GenericDocument#getCreationTimestampMillis()} on the document returned by {@link
- * #getDocument()}
+ * #getGenericDocument()}
* <li>{@link SearchSpec#RANKING_STRATEGY_RELEVANCE_SCORE} - an arbitrary double value where a
* higher value means more relevant
* <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} - the number of times usage has been
- * reported for the document returned by {@link #getDocument()}
+ * reported for the document returned by {@link #getGenericDocument()}
* <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} - the timestamp of the
* most recent usage that has been reported for the document returned by {@link
- * #getDocument()}
+ * #getGenericDocument()}
* </ul>
*
* @return Ranking signal of the document
@@ -354,13 +348,6 @@
return mFullText;
}
- /** @deprecated TODO(b/181887768): This method exists only for dogfooder transition. */
- @NonNull
- @Deprecated
- public MatchRange getExactMatchPosition() {
- return getExactMatchRange();
- }
-
/**
* Gets the exact {@link MatchRange} corresponding to the given entry.
*
@@ -387,13 +374,6 @@
return getSubstring(getExactMatchRange());
}
- /** @deprecated TODO(b/181887768): This method exists only for dogfooder transition. */
- @NonNull
- @Deprecated
- public MatchRange getSnippetPosition() {
- return getSnippetRange();
- }
-
/**
* Gets the snippet {@link MatchRange} corresponding to the given entry.
*
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 e840ffc..1324451 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -109,16 +109,6 @@
}
/**
- * TODO(b/181887768): This method exists only for dogfooder transition and must be removed.
- * @deprecated This method exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- @NonNull
- public Set<String> getSchemasNotVisibleToSystemUi() {
- return getSchemasNotDisplayedBySystem();
- }
-
- /**
* Returns all the schema types that are opted out of being displayed and visible on any system
* UI surface.
*/
@@ -222,17 +212,6 @@
}
/**
- * TODO(b/181887768): This method exists only for dogfooder transition and must be removed.
- * @deprecated This method exists only for dogfooder transition and must be removed.
- */
- @Deprecated
- @NonNull
- public Builder setSchemaTypeVisibilityForSystemUi(
- @NonNull String schemaType, boolean displayed) {
- return setSchemaTypeDisplayedBySystem(schemaType, displayed);
- }
-
- /**
* Sets whether or not documents from the provided {@code schemaType} will be displayed and
* visible on any system UI surface.
*
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
index 32d7e043..c9473bd 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
@@ -20,11 +20,12 @@
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.Migrator;
-import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
+import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -35,70 +36,84 @@
* @hide
*/
public final class SchemaMigrationUtil {
+ private static final String TAG = "AppSearchMigrateUtil";
+
private SchemaMigrationUtil() {}
- /** Returns all active {@link Migrator}s that need to be triggered in this migration. */
+ /**
+ * Finds out which incompatible schema type won't be migrated by comparing its current and final
+ * version number.
+ */
@NonNull
- public static Map<String, Migrator> getActiveMigrators(
- @NonNull Set<AppSearchSchema> existingSchemas,
+ public static Set<String> getUnmigratedIncompatibleTypes(
+ @NonNull Set<String> incompatibleSchemaTypes,
@NonNull Map<String, Migrator> migrators,
- int currentVersion,
- int finalVersion) {
- if (currentVersion == finalVersion) {
- return Collections.emptyMap();
- }
- Set<String> existingTypes = new ArraySet<>(existingSchemas.size());
- for (AppSearchSchema schema : existingSchemas) {
- existingTypes.add(schema.getSchemaType());
- }
-
- Map<String, Migrator> activeMigrators = new ArrayMap<>();
- for (Map.Entry<String, Migrator> entry : migrators.entrySet()) {
- // The device contains the source type, and we should trigger migration for the type.
- String schemaType = entry.getKey();
- Migrator migrator = entry.getValue();
- if (existingTypes.contains(schemaType)
- && migrator.shouldMigrate(currentVersion, finalVersion)) {
- activeMigrators.put(schemaType, migrator);
+ @NonNull Map<String, Integer> currentVersionMap,
+ @NonNull Map<String, Integer> finalVersionMap)
+ throws AppSearchException {
+ Set<String> unmigratedSchemaTypes = new ArraySet<>();
+ for (String unmigratedSchemaType : incompatibleSchemaTypes) {
+ Integer currentVersion = currentVersionMap.get(unmigratedSchemaType);
+ Integer finalVersion = finalVersionMap.get(unmigratedSchemaType);
+ if (currentVersion == null) {
+ // impossible, we have done something wrong.
+ throw new AppSearchException(
+ AppSearchResult.RESULT_UNKNOWN_ERROR,
+ "Cannot find the current version number for schema type: "
+ + unmigratedSchemaType);
+ }
+ if (finalVersion == null) {
+ // The schema doesn't exist in the SetSchemaRequest.
+ unmigratedSchemaTypes.add(unmigratedSchemaType);
+ continue;
+ }
+ // we don't have migrator or won't trigger migration for this schema type.
+ Migrator migrator = migrators.get(unmigratedSchemaType);
+ if (migrator == null
+ || !migrator.shouldMigrate(currentVersion, finalVersion)) {
+ unmigratedSchemaTypes.add(unmigratedSchemaType);
}
}
- return activeMigrators;
+ return Collections.unmodifiableSet(unmigratedSchemaTypes);
}
/**
- * Checks the setSchema() call won't delete any types or has incompatible types after all {@link
- * Migrator} has been triggered..
+ * Triggers upgrade or downgrade migration for the given schema type if its version stored in
+ * AppSearch is different with the version in the request.
+ *
+ * @return {@code True} if we trigger the migration for the given type.
*/
- public static void checkDeletedAndIncompatibleAfterMigration(
- @NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators)
+ public static boolean shouldTriggerMigration(
+ @NonNull String schemaType,
+ @NonNull Migrator migrator,
+ @NonNull Map<String, Integer> currentVersionMap,
+ @NonNull Map<String, Integer> finalVersionMap)
throws AppSearchException {
- Set<String> unmigratedIncompatibleTypes =
- new ArraySet<>(setSchemaResponse.getIncompatibleTypes());
- unmigratedIncompatibleTypes.removeAll(activeMigrators);
-
- Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes());
- unmigratedDeletedTypes.removeAll(activeMigrators);
-
- // check if there are any unmigrated incompatible types or deleted types. If there
- // are, we will getActiveMigratorsthrow an exception. That's the only case we
- // swallowed in the AppSearchImpl#setSchema().
- // Since the force override is false, the schema will not have been set if there are
- // any incompatible or deleted types.
- checkDeletedAndIncompatible(unmigratedDeletedTypes, unmigratedIncompatibleTypes);
+ Integer currentVersion = currentVersionMap.get(schemaType);
+ Integer finalVersion = finalVersionMap.get(schemaType);
+ if (currentVersion == null) {
+ Log.d(TAG, "The SchemaType: " + schemaType + " not present in AppSearch.");
+ return false;
+ }
+ if (finalVersion == null) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_INVALID_ARGUMENT,
+ "Receive a migrator for schema type : "
+ + schemaType
+ + ", but the schema doesn't exist in the request.");
+ }
+ return migrator.shouldMigrate(currentVersion, finalVersion);
}
- /** Checks the setSchema() call won't delete any types or has incompatible types. */
- public static void checkDeletedAndIncompatible(
- @NonNull Set<String> deletedTypes, @NonNull Set<String> incompatibleTypes)
- throws AppSearchException {
- if (deletedTypes.size() > 0 || incompatibleTypes.size() > 0) {
- String newMessage =
- "Schema is incompatible."
- + "\n Deleted types: "
- + deletedTypes
- + "\n Incompatible types: "
- + incompatibleTypes;
- throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage);
+ /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */
+ //TODO(b/182620003) remove this method once support migrate to another type
+ @NonNull
+ public static Map<String, Integer> buildVersionMap(
+ @NonNull Collection<AppSearchSchema> schemas, int version) {
+ Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size());
+ for (AppSearchSchema currentSchema : schemas) {
+ currentVersionMap.put(currentSchema.getSchemaType(), version);
}
+ return currentVersionMap;
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 6e3fb82..91ed6cd 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchMigrationHelper;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
@@ -31,10 +32,12 @@
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaResponse;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -49,6 +52,11 @@
import com.android.server.SystemService;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -125,7 +133,7 @@
schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
}
AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId);
- impl.setSchema(
+ SetSchemaResponse setSchemaResponse = impl.setSchema(
packageName,
databaseName,
schemas,
@@ -133,8 +141,8 @@
schemasPackageAccessible,
forceOverride,
schemaVersion);
- invokeCallbackOnResult(
- callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
+ invokeCallbackOnResult(callback,
+ AppSearchResult.newSuccessfulResult(setSchemaResponse.getBundle()));
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
} finally {
@@ -399,6 +407,98 @@
}
@Override
+ public void writeQueryResultsToFile(
+ @NonNull String packageName,
+ @NonNull String databaseName,
+ @NonNull ParcelFileDescriptor fileDescriptor,
+ @NonNull String queryExpression,
+ @NonNull Bundle searchSpecBundle,
+ @UserIdInt int userId,
+ @NonNull IAppSearchResultCallback callback) {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = handleIncomingUser(userId, callingUid);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ verifyCallingPackage(callingUid, packageName);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(callingUserId);
+ // we don't need to append the file. The file is always brand new.
+ try (DataOutputStream outputStream = new DataOutputStream(
+ new FileOutputStream(fileDescriptor.getFileDescriptor()))) {
+ SearchResultPage searchResultPage = impl.query(
+ packageName,
+ databaseName,
+ queryExpression,
+ new SearchSpec(searchSpecBundle));
+ while (!searchResultPage.getResults().isEmpty()) {
+ for (int i = 0; i < searchResultPage.getResults().size(); i++) {
+ AppSearchMigrationHelper.writeBundleToOutputStream(
+ outputStream, searchResultPage.getResults().get(i)
+ .getGenericDocument().getBundle());
+ }
+ searchResultPage = impl.getNextPage(searchResultPage.getNextPageToken());
+ }
+ }
+ invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
+ } catch (Throwable t) {
+ invokeCallbackOnError(callback, t);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
+ public void putDocumentsFromFile(
+ @NonNull String packageName,
+ @NonNull String databaseName,
+ @NonNull ParcelFileDescriptor fileDescriptor,
+ @UserIdInt int userId,
+ @NonNull IAppSearchResultCallback callback) {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = handleIncomingUser(userId, callingUid);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ verifyCallingPackage(callingUid, packageName);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(callingUserId);
+
+ GenericDocument document;
+ ArrayList<Bundle> migrationFailureBundles = new ArrayList<>();
+ try (DataInputStream inputStream = new DataInputStream(
+ new FileInputStream(fileDescriptor.getFileDescriptor()))) {
+ while (true) {
+ try {
+ document = AppSearchMigrationHelper
+ .readDocumentFromInputStream(inputStream);
+ } catch (EOFException e) {
+ // nothing wrong, we just finish the reading.
+ break;
+ }
+ try {
+ impl.putDocument(packageName, databaseName, document, /*logger=*/ null);
+ } catch (Throwable t) {
+ migrationFailureBundles.add(
+ new SetSchemaResponse.MigrationFailure.Builder()
+ .setNamespace(document.getNamespace())
+ .setSchemaType(document.getSchemaType())
+ .setUri(document.getUri())
+ .setAppSearchResult(
+ AppSearchResult.throwableToFailedResult(t))
+ .build().getBundle());
+ }
+ }
+ }
+ impl.persistToDisk();
+ invokeCallbackOnResult(callback,
+ AppSearchResult.newSuccessfulResult(migrationFailureBundles));
+ } catch (Throwable t) {
+ invokeCallbackOnError(callback, t);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
public void reportUsage(
@NonNull String packageName,
@NonNull String databaseName,
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index 8c953d1..cacf880 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -25,7 +25,6 @@
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.UserHandle;
-import android.os.storage.StorageManager;
import android.util.SparseArray;
import com.android.internal.R;
@@ -130,11 +129,7 @@
private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
// See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs
- //TODO(b/177685938):Switch from getDataUserCePackageDirectory to getDataSystemCeDirectory
- File userCeDir =
- Environment.getDataUserCePackageDirectory(
- StorageManager.UUID_PRIVATE_INTERNAL, userId, context.getPackageName());
- return new File(userCeDir, APP_SEARCH_DIR);
+ return new File(Environment.getDataSystemCeDirectory(userId), APP_SEARCH_DIR);
}
/**
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index 7847429..1ed26d6 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -156,7 +156,7 @@
AppSearchImpl.createPrefix(PACKAGE_NAME, DATABASE_NAME);
/** Namespace of documents that contain visibility settings */
- private static final String NAMESPACE = GenericDocument.DEFAULT_NAMESPACE;
+ private static final String NAMESPACE = "";
/**
* Prefix to add to all visibility document uri's. IcingSearchEngine doesn't allow empty uri's.
@@ -337,9 +337,9 @@
Preconditions.checkNotNull(schemasPackageAccessible);
// Persist the document
- GenericDocument.Builder visibilityDocument =
- new GenericDocument.Builder(/*uri=*/ addUriPrefix(prefix), VISIBILITY_TYPE)
- .setNamespace(NAMESPACE);
+ GenericDocument.Builder<?> visibilityDocument =
+ new GenericDocument.Builder<>(
+ NAMESPACE, /*uri=*/ addUriPrefix(prefix), VISIBILITY_TYPE);
if (!schemasNotPlatformSurfaceable.isEmpty()) {
visibilityDocument.setPropertyString(
NOT_PLATFORM_SURFACEABLE_PROPERTY,
@@ -351,17 +351,16 @@
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
for (int i = 0; i < entry.getValue().size(); i++) {
- GenericDocument packageAccessibleDocument =
- new GenericDocument.Builder(/*uri=*/ "", PACKAGE_ACCESSIBLE_TYPE)
- .setNamespace(NAMESPACE)
- .setPropertyString(
- PACKAGE_NAME_PROPERTY,
- entry.getValue().get(i).getPackageName())
- .setPropertyBytes(
- SHA_256_CERT_PROPERTY,
- entry.getValue().get(i).getSha256Certificate())
- .setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, entry.getKey())
- .build();
+ GenericDocument packageAccessibleDocument = new GenericDocument.Builder<>(
+ NAMESPACE, /*uri=*/ "", PACKAGE_ACCESSIBLE_TYPE)
+ .setPropertyString(
+ PACKAGE_NAME_PROPERTY,
+ entry.getValue().get(i).getPackageName())
+ .setPropertyBytes(
+ SHA_256_CERT_PROPERTY,
+ entry.getValue().get(i).getSha256Certificate())
+ .setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, entry.getKey())
+ .build();
packageAccessibleDocuments.add(packageAccessibleDocument);
}
schemaToPackageIdentifierMap.put(entry.getKey(), new ArraySet<>(entry.getValue()));
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index b51c2aa..f766fcf 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -32,6 +32,7 @@
},
libs: [
"app-compat-annotations",
+ "framework-connectivity.stubs.module_lib",
"framework-minus-apex",
"unsupportedappusage",
],
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 78c5b15..3ea1922 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -29,6 +30,7 @@
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -42,7 +44,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.Objects;
import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
/**
* This class provides access to the system alarm services. These allow you
@@ -194,6 +198,15 @@
public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5;
/**
+ * Flag for alarms: Used to mark prioritized alarms. These alarms will get to execute while idle
+ * and can be sent separately from other alarms that may be already due at the time.
+ * These alarms can be set via
+ * {@link #setPrioritized(int, long, long, String, Executor, OnAlarmListener)}
+ * @hide
+ */
+ public static final int FLAG_PRIORITIZE = 1 << 6;
+
+ /**
* For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
* {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
* {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
@@ -227,15 +240,15 @@
final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
final OnAlarmListener mListener;
- Handler mHandler;
+ Executor mExecutor;
IAlarmCompleteListener mCompletion;
public ListenerWrapper(OnAlarmListener listener) {
mListener = listener;
}
- public void setHandler(Handler h) {
- mHandler = h;
+ void setExecutor(Executor e) {
+ mExecutor = e;
}
public void cancel() {
@@ -250,7 +263,7 @@
public void doAlarm(IAlarmCompleteListener alarmManager) {
mCompletion = alarmManager;
- mHandler.post(this);
+ mExecutor.execute(this);
}
@Override
@@ -368,7 +381,7 @@
*/
public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
- null, null, null);
+ (Handler) null, null, null);
}
/**
@@ -457,7 +470,7 @@
public void setRepeating(@AlarmType int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
- null, null, null, null, null);
+ null, null, (Handler) null, null, null);
}
/**
@@ -507,7 +520,7 @@
public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
PendingIntent operation) {
setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
- null, null, null, null, null);
+ null, null, (Handler) null, null, null);
}
/**
@@ -526,6 +539,53 @@
}
/**
+ * Schedule an alarm that is prioritized by the system while the device is in power saving modes
+ * such as battery saver and device idle (doze).
+ *
+ * <p>
+ * Apps that use this are not guaranteed to get all alarms as requested during power saving
+ * modes, i.e. the system may still impose restrictions on how frequently these alarms will go
+ * off for a particular application, like requiring a certain minimum duration be elapsed
+ * between consecutive alarms. This duration will be normally be in the order of a few minutes.
+ *
+ * <p>
+ * When the system wakes up to deliver these alarms, it may not deliver any of the other pending
+ * alarms set earlier by the calling app, even the special ones set via
+ * {@link #setAndAllowWhileIdle(int, long, PendingIntent)} or
+ * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)}. So the caller should not
+ * expect these to arrive in any relative order to its other alarms.
+ *
+ * @param type type of alarm
+ * @param windowStartMillis The earliest time, in milliseconds, that the alarm should
+ * be delivered, expressed in the appropriate clock's units (depending on the alarm
+ * type).
+ * @param windowLengthMillis The length of the requested delivery window,
+ * in milliseconds. The alarm will be delivered no later than this many
+ * milliseconds after {@code windowStartMillis}. Note that this parameter
+ * is a <i>duration,</i> not the timestamp of the end of the window.
+ * @param tag string describing the alarm, used for logging and battery-use
+ * attribution
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * called when the alarm time is reached. A given OnAlarmListener instance can
+ * only be the target of a single pending alarm, just as a given PendingIntent
+ * can only be used with one alarm at a time.
+ * @param executor {@link Executor} on which to execute the listener's onAlarm()
+ * callback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SCHEDULE_PRIORITIZED_ALARM)
+ public void setPrioritized(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @NonNull String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(tag);
+ Objects.requireNonNull(listener);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, FLAG_PRIORITIZE, null, listener,
+ tag, executor, null, null);
+ }
+
+ /**
* Schedule an alarm to be delivered precisely at the stated time.
*
* <p>
@@ -565,7 +625,7 @@
*/
@RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null,
null, null);
}
@@ -645,7 +705,7 @@
@RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
- null, null, null, null, info);
+ null, null, (Handler) null, null, info);
}
/** @hide */
@@ -654,7 +714,7 @@
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
long intervalMillis, PendingIntent operation, WorkSource workSource) {
setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
- null, workSource, null);
+ (Handler) null, workSource, null);
}
/**
@@ -698,6 +758,15 @@
long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
String listenerTag, Handler targetHandler, WorkSource workSource,
AlarmClockInfo alarmClock) {
+ final Handler handlerToUse = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation, listener,
+ listenerTag, new HandlerExecutor(handlerToUse), workSource, alarmClock);
+ }
+
+ private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis,
+ long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
+ String listenerTag, Executor targetExecutor, WorkSource workSource,
+ AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
@@ -726,9 +795,7 @@
sWrappers.put(listener, new WeakReference<>(recipientWrapper));
}
}
-
- final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
- recipientWrapper.setHandler(handler);
+ recipientWrapper.setExecutor(targetExecutor);
}
try {
@@ -834,7 +901,7 @@
public void setInexactRepeating(@AlarmType int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
- null, null, null, null);
+ null, (Handler) null, null, null);
}
/**
@@ -884,7 +951,7 @@
public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
- operation, null, null, null, null, null);
+ operation, null, null, (Handler) null, null, null);
}
/**
@@ -945,7 +1012,7 @@
public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
- null, null, null, null, null);
+ null, null, (Handler) null, null, null);
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 58fc874..3c9496f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -23,6 +23,7 @@
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
+import static android.app.AlarmManager.FLAG_PRIORITIZE;
import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.INTERVAL_DAY;
import static android.app.AlarmManager.INTERVAL_HOUR;
@@ -100,6 +101,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -221,6 +223,7 @@
AlarmHandler mHandler;
AppWakeupHistory mAppWakeupHistory;
AppWakeupHistory mAllowWhileIdleHistory;
+ private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray();
ClockReceiver mClockReceiver;
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
IBinder.DeathRecipient mListenerDeathRecipient;
@@ -432,6 +435,8 @@
@VisibleForTesting
static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps";
+ @VisibleForTesting
+ static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -470,6 +475,8 @@
// TODO (b/171306433): Change to true by default.
private static final boolean DEFAULT_CRASH_NON_CLOCK_APPS = false;
+ private static final long DEFAULT_PRIORITY_ALARM_DELAY = 9 * 60_000;
+
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -525,6 +532,12 @@
*/
public boolean CRASH_NON_CLOCK_APPS = DEFAULT_CRASH_NON_CLOCK_APPS;
+ /**
+ * Minimum delay between two slots that an app can get for their prioritized alarms, while
+ * the device is in doze.
+ */
+ public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY;
+
private long mLastAllowWhileIdleWhitelistDuration = -1;
Constants() {
@@ -662,6 +675,10 @@
CRASH_NON_CLOCK_APPS = properties.getBoolean(KEY_CRASH_NON_CLOCK_APPS,
DEFAULT_CRASH_NON_CLOCK_APPS);
break;
+ case KEY_PRIORITY_ALARM_DELAY:
+ PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY,
+ DEFAULT_PRIORITY_ALARM_DELAY);
+ break;
default:
if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
// The quotas need to be updated in order, so we can't just rely
@@ -809,6 +826,11 @@
pw.print(KEY_CRASH_NON_CLOCK_APPS, CRASH_NON_CLOCK_APPS);
pw.println();
+ pw.print(KEY_PRIORITY_ALARM_DELAY);
+ pw.print("=");
+ TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw);
+ pw.println();
+
pw.decreaseIndent();
}
@@ -1794,6 +1816,11 @@
batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
alarm.sourcePackage, userId, quota) + window;
}
+ } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
+ final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0);
+ batterySaverPolicyElapsed = (lastDispatch == 0)
+ ? nowElapsed
+ : lastDispatch + mConstants.PRIORITY_ALARM_DELAY;
} else {
// Not allowed.
batterySaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY;
@@ -1849,6 +1876,12 @@
alarm.sourcePackage, userId, quota) + window;
deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
}
+ } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
+ final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0);
+ final long whenAllowed = (lastDispatch == 0)
+ ? nowElapsed
+ : lastDispatch + mConstants.PRIORITY_ALARM_DELAY;
+ deviceIdlePolicyTime = Math.min(whenAllowed, mPendingIdleUntil.getWhenElapsed());
} else {
// Not allowed.
deviceIdlePolicyTime = mPendingIdleUntil.getWhenElapsed();
@@ -2025,7 +2058,12 @@
// make sure the caller is allowed to use the requested kind of alarm, and also
// decide what quota and broadcast options to use.
Bundle idleOptions = null;
- if (exact || allowWhileIdle) {
+ if ((flags & FLAG_PRIORITIZE) != 0) {
+ getContext().enforcePermission(
+ Manifest.permission.SCHEDULE_PRIORITIZED_ALARM,
+ Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
+ flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+ } else if (exact || allowWhileIdle) {
final boolean needsPermission;
boolean lowerQuota;
if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
@@ -2107,6 +2145,7 @@
flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~FLAG_ALLOW_WHILE_IDLE;
flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
+ flags &= ~FLAG_PRIORITIZE;
idleOptions = null;
}
@@ -2489,6 +2528,19 @@
pw.println("Allow while idle history:");
mAllowWhileIdleHistory.dump(pw, nowELAPSED);
+ if (mLastPriorityAlarmDispatch.size() > 0) {
+ pw.println("Last priority alarm dispatches:");
+ pw.increaseIndent();
+ for (int i = 0; i < mLastPriorityAlarmDispatch.size(); i++) {
+ pw.print("UID: ");
+ UserHandle.formatUid(pw, mLastPriorityAlarmDispatch.keyAt(i));
+ pw.print(": ");
+ TimeUtils.formatDuration(mLastPriorityAlarmDispatch.valueAt(i), nowELAPSED, pw);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
if (mLog.dump(pw, "Recent problems:")) {
pw.println();
}
@@ -3303,6 +3355,11 @@
mPendingBackgroundAlarms.removeAt(i);
}
}
+ for (int i = mLastPriorityAlarmDispatch.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mLastPriorityAlarmDispatch.keyAt(i)) == userHandle) {
+ mLastPriorityAlarmDispatch.removeAt(i);
+ }
+ }
if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) {
mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
if (mPendingIdleUntil != null) {
@@ -4103,6 +4160,7 @@
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
sdFilter.addAction(Intent.ACTION_USER_STOPPED);
+ sdFilter.addAction(Intent.ACTION_UID_REMOVED);
getContext().registerReceiver(this, sdFilter);
}
@@ -4132,6 +4190,9 @@
mAllowWhileIdleHistory.removeForUser(userHandle);
}
return;
+ case Intent.ACTION_UID_REMOVED:
+ mLastPriorityAlarmDispatch.delete(uid);
+ return;
case Intent.ACTION_PACKAGE_REMOVED:
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
// This package is being updated; don't kill its alarms.
@@ -4522,11 +4583,11 @@
if (inflight.isBroadcast()) {
notifyBroadcastAlarmPendingLocked(alarm.uid);
}
- if (isAllowedWhileIdleRestricted(alarm)) {
- final boolean doze = (mPendingIdleUntil != null);
- final boolean batterySaver = (mAppStateTracker != null
- && mAppStateTracker.isForceAllAppsStandbyEnabled());
- if (doze || batterySaver) {
+ final boolean doze = (mPendingIdleUntil != null);
+ final boolean batterySaver = (mAppStateTracker != null
+ && mAppStateTracker.isForceAllAppsStandbyEnabled());
+ if (doze || batterySaver) {
+ if (isAllowedWhileIdleRestricted(alarm)) {
// Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the
// device was in doze or battery saver.
mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
@@ -4538,6 +4599,16 @@
return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
|| (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a));
});
+ } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
+ mLastPriorityAlarmDispatch.put(alarm.creatorUid, nowELAPSED);
+ mAlarmStore.updateAlarmDeliveries(a -> {
+ if (a.creatorUid != alarm.creatorUid
+ || (alarm.flags & FLAG_PRIORITIZE) == 0) {
+ return false;
+ }
+ return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
+ || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a));
+ });
}
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
diff --git a/core/api/current.txt b/core/api/current.txt
index feec087..935cf70 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1009,7 +1009,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 nativeHeapZeroInitialized = 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
@@ -7013,6 +7013,7 @@
method public void onBugreportShared(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String);
method public void onBugreportSharingDeclined(@NonNull android.content.Context, @NonNull android.content.Intent);
method @Nullable public String onChoosePrivateKeyAlias(@NonNull android.content.Context, @NonNull android.content.Intent, int, @Nullable android.net.Uri, @Nullable String);
+ method public void onComplianceAcknowledgementRequired(@NonNull android.content.Context, @NonNull android.content.Intent);
method @Nullable public CharSequence onDisableRequested(@NonNull android.content.Context, @NonNull android.content.Intent);
method public void onDisabled(@NonNull android.content.Context, @NonNull android.content.Intent);
method public void onEnabled(@NonNull android.content.Context, @NonNull android.content.Intent);
@@ -7067,6 +7068,7 @@
}
public class DevicePolicyManager {
+ method public void acknowledgeDeviceCompliant();
method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
@@ -7185,6 +7187,7 @@
method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName);
method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName);
+ method public boolean isComplianceAcknowledgementRequired();
method public boolean isDeviceIdAttestationSupported();
method public boolean isDeviceOwnerApp(String);
method public boolean isEnterpriseNetworkPreferenceEnabled();
@@ -11864,7 +11867,7 @@
method public static CharSequence getCategoryTitle(android.content.Context, int);
method public int getGwpAsanMode();
method public int getMemtagMode();
- method @Nullable public Boolean isNativeHeapZeroInit();
+ method public int getNativeHeapZeroInitialized();
method public boolean isProfileableByShell();
method public boolean isResourceOverlay();
method public boolean isVirtualPreload();
@@ -11919,6 +11922,9 @@
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 static final int ZEROINIT_DEFAULT = -1; // 0xffffffff
+ field public static final int ZEROINIT_DISABLED = 0; // 0x0
+ field public static final int ZEROINIT_ENABLED = 1; // 0x1
field public String appComponentFactory;
field public String backupAgentName;
field public int category;
@@ -13373,7 +13379,8 @@
method public void setTo(android.content.res.Resources.Theme);
}
- public class TypedArray {
+ public class TypedArray implements java.lang.AutoCloseable {
+ method public void close();
method public boolean getBoolean(@StyleableRes int, boolean);
method public int getChangingConfigurations();
method @ColorInt public int getColor(@StyleableRes int, @ColorInt int);
@@ -17859,6 +17866,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_DISTORTION;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_DISTORTION_MAXIMUM_RESOLUTION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_FACING;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INFO_AVAILABLE_APERTURES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INFO_AVAILABLE_FILTER_DENSITIES;
@@ -17868,6 +17876,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_HYPERFOCAL_DISTANCE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INTRINSIC_CALIBRATION;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INTRINSIC_CALIBRATION_MAXIMUM_RESOLUTION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_POSE_REFERENCE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_ROTATION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_TRANSLATION;
@@ -17887,9 +17896,11 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SENSOR_AVAILABLE_TEST_PATTERN_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM1;
@@ -17899,13 +17910,17 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX1;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX2;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SENSOR_INFO_BINNING_FACTOR;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_COLOR_FILTER_ARRANGEMENT;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> SENSOR_INFO_LENS_SHADING_APPLIED;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Long> SENSOR_INFO_MAX_FRAME_DURATION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.SizeF> SENSOR_INFO_PHYSICAL_SIZE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> SENSOR_INFO_SENSITIVITY_RANGE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_TIMESTAMP_SOURCE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_WHITE_LEVEL;
@@ -18202,8 +18217,10 @@
field public static final int REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING = 4; // 0x4
field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3
field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5
+ field public static final int REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING = 17; // 0x11
field public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13; // 0xd
field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe
+ field public static final int REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR = 16; // 0x10
field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7
field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0
field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1
@@ -18221,6 +18238,8 @@
field public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB = 0; // 0x0
field public static final int SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME = 1; // 0x1
field public static final int SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN = 0; // 0x0
+ field public static final int SENSOR_PIXEL_MODE_DEFAULT = 0; // 0x0
+ field public static final int SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION = 1; // 0x1
field public static final int SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER = 10; // 0xa
field public static final int SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT = 14; // 0xe
field public static final int SENSOR_REFERENCE_ILLUMINANT1_D50 = 23; // 0x17
@@ -18350,6 +18369,7 @@
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SCALER_ROTATE_AND_CROP;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Long> SENSOR_FRAME_DURATION;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SENSOR_PIXEL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SENSOR_SENSITIVITY;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<int[]> SENSOR_TEST_PATTERN_DATA;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SENSOR_TEST_PATTERN_MODE;
@@ -18453,6 +18473,8 @@
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> SENSOR_GREEN_SPLIT;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Rational[]> SENSOR_NEUTRAL_COLOR_POINT;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Double,java.lang.Double>[]> SENSOR_NOISE_PROFILE;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_PIXEL_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> SENSOR_RAW_BINNING_FACTOR_USED;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_ROLLING_SHUTTER_SKEW;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_SENSITIVITY;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> SENSOR_TEST_PATTERN_DATA;
@@ -18627,6 +18649,7 @@
ctor public OutputConfiguration(@NonNull android.view.Surface);
ctor public OutputConfiguration(int, @NonNull android.view.Surface);
ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+ method public void addSensorPixelModeUsed(int);
method public void addSurface(@NonNull android.view.Surface);
method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
method public int describeContents();
@@ -18635,6 +18658,7 @@
method @Nullable public android.view.Surface getSurface();
method public int getSurfaceGroupId();
method @NonNull public java.util.List<android.view.Surface> getSurfaces();
+ method public void removeSensorPixelModeUsed(int);
method public void removeSurface(@NonNull android.view.Surface);
method public void setPhysicalCameraId(@Nullable String);
method public void writeToParcel(android.os.Parcel, int);
@@ -19805,7 +19829,8 @@
method public boolean hasSpeed();
method public boolean hasSpeedAccuracy();
method public boolean hasVerticalAccuracy();
- method public boolean isFromMockProvider();
+ method @Deprecated public boolean isFromMockProvider();
+ method public boolean isMock();
method @Deprecated public void removeAccuracy();
method @Deprecated public void removeAltitude();
method @Deprecated public void removeBearing();
@@ -19821,6 +19846,7 @@
method public void setExtras(@Nullable android.os.Bundle);
method public void setLatitude(double);
method public void setLongitude(double);
+ method public void setMock(boolean);
method public void setProvider(String);
method public void setSpeed(float);
method public void setSpeedAccuracyMetersPerSecond(float);
@@ -21345,7 +21371,9 @@
method @NonNull public android.media.MediaFormat getOutputFormat(int);
method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
method @Nullable public android.media.Image getOutputImage(int);
+ method @Nullable public android.media.MediaCodec.ParameterDescriptor getParameterDescriptor(@NonNull String);
method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
+ method @NonNull public java.util.List<java.lang.String> getSupportedVendorParameters();
method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
@@ -21365,6 +21393,8 @@
method public void signalEndOfInputStream();
method public void start();
method public void stop();
+ method public void subscribeToVendorParameters(@NonNull java.util.List<java.lang.String>);
+ method public void unsubscribeFromVendorParameters(@NonNull java.util.List<java.lang.String>);
field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2
field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4
field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
@@ -21492,6 +21522,11 @@
method public long getPresentationTimeUs();
}
+ public static class MediaCodec.ParameterDescriptor {
+ method @NonNull public String getName();
+ method public int getType();
+ }
+
public final class MediaCodec.QueueRequest {
method public void queue();
method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer);
@@ -21817,6 +21852,7 @@
method public android.util.Range<java.lang.Integer> getQualityRange();
method public boolean isBitrateModeSupported(int);
field public static final int BITRATE_MODE_CBR = 2; // 0x2
+ field public static final int BITRATE_MODE_CBR_FD = 3; // 0x3
field public static final int BITRATE_MODE_CQ = 0; // 0x0
field public static final int BITRATE_MODE_VBR = 1; // 0x1
}
@@ -34907,6 +34943,7 @@
field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
+ field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "com.android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS";
@@ -37476,6 +37513,7 @@
method public void onDisconnected();
method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
+ method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final String SERVICE_META_DATA = "android.autofill";
}
@@ -37751,6 +37789,22 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
+ public final class SavedDatasetsInfo {
+ ctor public SavedDatasetsInfo(@NonNull String, @IntRange(from=0) int);
+ method @IntRange(from=0) public int getCount();
+ method @NonNull public String getType();
+ field public static final String TYPE_OTHER = "other";
+ field public static final String TYPE_PASSWORDS = "passwords";
+ }
+
+ public interface SavedDatasetsInfoCallback {
+ method public void onError(int);
+ method public void onSuccess(@NonNull java.util.Set<android.service.autofill.SavedDatasetsInfo>);
+ field public static final int ERROR_NEEDS_USER_ACTION = 2; // 0x2
+ field public static final int ERROR_OTHER = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+ }
+
public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
ctor public TextValueSanitizer(@NonNull java.util.regex.Pattern, @NonNull String);
method public int describeContents();
@@ -42316,7 +42370,7 @@
}
public static interface TelephonyCallback.DisplayInfoListener {
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
+ method public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
}
public static interface TelephonyCallback.EmergencyNumberListListener {
@@ -48310,6 +48364,7 @@
method protected int computeVerticalScrollRange();
method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
method public void createContextMenu(android.view.ContextMenu);
+ method @Nullable public android.view.translation.ViewTranslationRequest createTranslationRequest(@NonNull int[]);
method @Deprecated public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
@@ -48530,6 +48585,7 @@
method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarThumbDrawable();
method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarTrackDrawable();
method public int getVerticalScrollbarWidth();
+ method @Nullable public android.view.translation.ViewTranslationCallback getViewTranslationCallback();
method public android.view.ViewTreeObserver getViewTreeObserver();
method public int getVisibility();
method public final int getWidth();
@@ -48673,6 +48729,7 @@
method public void onStartTemporaryDetach();
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
+ method public void onTranslationResponse(@NonNull android.view.translation.ViewTranslationResponse);
method @CallSuper public void onVisibilityAggregated(boolean);
method protected void onVisibilityChanged(@NonNull android.view.View, int);
method public void onWindowFocusChanged(boolean);
@@ -48881,6 +48938,7 @@
method public void setVerticalScrollbarPosition(int);
method public void setVerticalScrollbarThumbDrawable(@Nullable android.graphics.drawable.Drawable);
method public void setVerticalScrollbarTrackDrawable(@Nullable android.graphics.drawable.Drawable);
+ method public void setViewTranslationCallback(@NonNull android.view.translation.ViewTranslationCallback);
method public void setVisibility(int);
method @Deprecated public void setWillNotCacheDrawing(boolean);
method public void setWillNotDraw(boolean);
@@ -52703,6 +52761,12 @@
method public void onStarted(@NonNull String, @NonNull String);
}
+ @UiThread public interface ViewTranslationCallback {
+ method public boolean onClearTranslation(@NonNull android.view.View);
+ method public boolean onHideTranslation(@NonNull android.view.View);
+ method public boolean onShowTranslation(@NonNull android.view.View);
+ }
+
public final class ViewTranslationRequest implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.view.autofill.AutofillId getAutofillId();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6ba9478..e8bdd41 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3,6 +3,7 @@
public static final class Manifest.permission {
field public static final String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS";
+ field public static final String ACCESS_BLOBS_ACROSS_USERS = "android.permission.ACCESS_BLOBS_ACROSS_USERS";
field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
@@ -186,6 +187,7 @@
field public static final String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS";
field public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY";
field public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission.OPEN_ACCESSIBILITY_DETAILS_SETTINGS";
+ field public static final String OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD = "android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD";
field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT";
field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
@@ -243,6 +245,7 @@
field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
field public static final String ROTATE_SURFACE_FLINGER = "android.permission.ROTATE_SURFACE_FLINGER";
+ field public static final String SCHEDULE_PRIORITIZED_ALARM = "android.permission.SCHEDULE_PRIORITIZED_ALARM";
field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
@@ -422,6 +425,7 @@
public class AlarmManager {
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource);
+ method @RequiresPermission(android.Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) public void setPrioritized(int, long, long, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener);
}
public class AppOpsManager {
@@ -951,6 +955,9 @@
field @Deprecated public static final int PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER = 3; // 0x3
field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2
field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0
+ field public static final String REQUIRED_APP_MANAGED_DEVICE = "android.app.REQUIRED_APP_MANAGED_DEVICE";
+ field public static final String REQUIRED_APP_MANAGED_PROFILE = "android.app.REQUIRED_APP_MANAGED_PROFILE";
+ field public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER";
field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
@@ -1223,6 +1230,21 @@
method public static boolean isChangeEnabled(long);
method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, @NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, int);
+ method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void setPackageOverride(@NonNull String, @NonNull java.util.Map<java.lang.Long,android.app.compat.PackageOverride>);
+ }
+
+ public final class PackageOverride {
+ method public long getMaxVersionCode();
+ method public long getMinVersionCode();
+ method public boolean isEnabled();
+ }
+
+ public static final class PackageOverride.Builder {
+ ctor public PackageOverride.Builder();
+ method @NonNull public android.app.compat.PackageOverride build();
+ method @NonNull public android.app.compat.PackageOverride.Builder setEnabled(boolean);
+ method @NonNull public android.app.compat.PackageOverride.Builder setMaxVersionCode(long);
+ method @NonNull public android.app.compat.PackageOverride.Builder setMinVersionCode(long);
}
}
@@ -1953,7 +1975,6 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnect(android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
- method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
}
public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
@@ -2180,6 +2201,7 @@
package android.companion {
public final class CompanionDeviceManager {
+ method @RequiresPermission("android.permission.ASSOCIATE_COMPANION_DEVICES") public boolean associate(@NonNull String, @NonNull android.net.MacAddress);
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, int);
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
}
@@ -2807,7 +2829,9 @@
method @NonNull public android.content.pm.SuspendDialogInfo.Builder setMessage(@StringRes int);
method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonAction(int);
method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonText(@StringRes int);
+ method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonText(@NonNull String);
method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@StringRes int);
+ method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@NonNull String);
}
}
@@ -2873,7 +2897,7 @@
}
public final class 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.DOMAIN_VERIFICATION_AGENT) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames();
method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -4827,7 +4851,7 @@
public class Location implements android.os.Parcelable {
method public boolean isComplete();
method public void makeComplete();
- method public void setIsFromMockProvider(boolean);
+ method @Deprecated public void setIsFromMockProvider(boolean);
field @Deprecated public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
@@ -5235,7 +5259,6 @@
method @Nullable public String getClientPackageName();
method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
- method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback);
method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
method public void startScan();
method public void stopScan();
@@ -5259,6 +5282,10 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void ensureDefaultRingtones(@NonNull android.content.Context);
}
+ public final class RouteDiscoveryPreference implements android.os.Parcelable {
+ field public static final android.media.RouteDiscoveryPreference EMPTY;
+ }
+
}
package android.media.audiofx {
@@ -7577,9 +7604,11 @@
method public void notifyAlertReached();
method public void notifyLimitReached();
method public void notifyStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats);
+ method public void notifyWarningReached();
method public abstract void onRequestStatsUpdate(int);
method public abstract void onSetAlert(long);
method public abstract void onSetLimit(@NonNull String, long);
+ method public void onSetWarningAndLimit(@NonNull String, long, long);
field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff
}
@@ -8961,6 +8990,7 @@
field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
+ field public static final String NAMESPACE_MEDIA = "media";
field public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
field public static final String NAMESPACE_OTA = "ota";
@@ -10350,6 +10380,7 @@
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
+ method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
@@ -10378,7 +10409,9 @@
}
public static class AlwaysOnHotwordDetector.EventPayload {
+ method @Nullable public android.os.ParcelFileDescriptor getAudioStream();
method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
+ method @Nullable public android.service.voice.HotwordDetectedResult getHotwordDetectedResult();
method @Nullable public byte[] getTriggerAudio();
}
@@ -12303,6 +12336,20 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR;
}
+ public final class NrQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes {
+ method public int describeContents();
+ method public int get5Qi();
+ method public long getAveragingWindow();
+ method public long getGuaranteedDownlinkBitRate();
+ method public long getGuaranteedUplinkBitRate();
+ method public long getMaxDownlinkBitRate();
+ method public long getMaxUplinkBitRate();
+ method public int getQfi();
+ method @NonNull public java.util.List<java.net.InetSocketAddress> getRemoteAddresses();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.NrQosSessionAttributes> CREATOR;
+ }
+
public abstract class QualifiedNetworksService extends android.app.Service {
ctor public QualifiedNetworksService();
method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
@@ -14050,7 +14097,7 @@
package android.uwb {
public final class AngleMeasurement implements android.os.Parcelable {
- ctor public AngleMeasurement(double, double, double);
+ ctor public AngleMeasurement(@FloatRange(from=-3.141592653589793, to=3.141592653589793) double, @FloatRange(from=0.0, to=3.141592653589793) double, @FloatRange(from=0.0, to=1.0) double);
method public int describeContents();
method @FloatRange(from=0.0, to=1.0) public double getConfidenceLevel();
method @FloatRange(from=0.0, to=3.141592653589793) public double getErrorRadians();
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 3926e39..b50b8dd 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -48,6 +48,14 @@
}
+package android.bluetooth {
+
+ public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
+ }
+
+}
+
package android.content {
public class Intent implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 47de040..5964f71 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -135,4 +135,20 @@
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * Returns a list of supported game modes for a given package.
+ * <p>
+ * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode int[] getAvailableGameModes(@NonNull String packageName) {
+ try {
+ return mService.getAvailableGameModes(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
}
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index c8e1478..4bf8a3f 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -22,4 +22,5 @@
interface IGameManagerService {
int getGameMode(String packageName, int userId);
void setGameMode(String packageName, int gameMode, int userId);
+ int[] getAvailableGameModes(String packageName);
}
\ No newline at end of file
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 747a2de..da64dcd 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -524,6 +524,16 @@
"android.app.action.OPERATION_SAFETY_STATE_CHANGED";
/**
+ * Broadcast action: notify the profile owner on an organization-owned device that it needs to
+ * acknowledge device compliance.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED =
+ "android.app.action.COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED";
+
+ /**
* An {@code int} extra specifying an {@link OperationSafetyReason}.
*
* @hide
@@ -1116,6 +1126,29 @@
onOperationSafetyStateChanged(context, reason, isSafe);
}
+ /**
+ * Called to notify a profile owner of an organization-owned device that it needs to acknowledge
+ * device compliance to allow the user to turn the profile off if needed according to the
+ * maximum profile time off policy.
+ *
+ * Default implementation acknowledges compliance immediately. DPC may prefer to override this
+ * implementation to delay acknowledgement until a successful policy sync. Until compliance is
+ * acknowledged the user is still free to turn the profile off, but the timer won't be reset,
+ * so personal apps will be suspended sooner. This callback is delivered using a foreground
+ * broadcast and should be handled quickly.
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param intent The received intent as per {@link #onReceive}.
+ *
+ * @see DevicePolicyManager#acknowledgeDeviceCompliant()
+ * @see DevicePolicyManager#isComplianceAcknowledgementRequired()
+ * @see DevicePolicyManager#setManagedProfileMaximumTimeOff(ComponentName, long)
+ */
+ public void onComplianceAcknowledgementRequired(
+ @NonNull Context context, @NonNull Intent intent) {
+ getManager(context).acknowledgeDeviceCompliant();
+ }
+
private boolean hasRequiredExtra(Intent intent, String extra) {
if (intent.hasExtra(extra)) return true;
@@ -1204,6 +1237,8 @@
intent.getParcelableExtra(Intent.EXTRA_USER));
} else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) {
onOperationSafetyStateChanged(context, intent);
+ } else if (ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED.equals(action)) {
+ onComplianceAcknowledgementRequired(context, intent);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d3534f9..26e6741 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3306,6 +3306,41 @@
"android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
/**
+ * A {@code boolean} metadata to be included in a mainline module's {@code <application>}
+ * manifest element, which declares that the module should be considered a required app for
+ * managed users.
+ * <p>Being declared as a required app prevents removal of this package during the
+ * provisioning process.
+ * @hide
+ */
+ @SystemApi
+ public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER";
+
+ /**
+ * A {@code boolean} metadata to be included in a mainline module's {@code <application>}
+ * manifest element, which declares that the module should be considered a required app for
+ * managed devices.
+ * <p>Being declared as a required app prevents removal of this package during the
+ * provisioning process.
+ * @hide
+ */
+ @SystemApi
+ public static final String REQUIRED_APP_MANAGED_DEVICE =
+ "android.app.REQUIRED_APP_MANAGED_DEVICE";
+
+ /**
+ * A {@code boolean} metadata to be included in a mainline module's {@code <application>}
+ * manifest element, which declares that the module should be considered a required app for
+ * managed profiles.
+ * <p>Being declared as a required app prevents removal of this package during the
+ * provisioning process.
+ * @hide
+ */
+ @SystemApi
+ public static final String REQUIRED_APP_MANAGED_PROFILE =
+ "android.app.REQUIRED_APP_MANAGED_PROFILE";
+
+ /**
* Called by an application that is administering the device to set the password restrictions it
* is imposing. After setting this, the user will not be able to enter a new password that is
* not at least as restrictive as what has been set. Note that the current password will remain
@@ -13374,6 +13409,63 @@
}
/**
+ * Called by a profile owner of an organization-owned managed profile to acknowledge that the
+ * device is compliant and the user can turn the profile off if needed according to the maximum
+ * time off policy.
+ *
+ * This method should be called when the device is deemed compliant after getting
+ * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)} callback in
+ * case it is overridden. Before this method is called the user is still free to turn the
+ * profile off, but the timer won't be reset, so personal apps will be suspended sooner.
+ *
+ * DPCs only need acknowledging device compliance if they override
+ * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)}, otherwise
+ * compliance is acknowledged automatically.
+ *
+ * @throws IllegalStateException if the user isn't unlocked
+ * @see #isComplianceAcknowledgementRequired()
+ * @see #setManagedProfileMaximumTimeOff(ComponentName, long)
+ * @see DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)
+ */
+ public void acknowledgeDeviceCompliant() {
+ throwIfParentInstance("acknowledgeDeviceCompliant");
+ if (mService != null) {
+ try {
+ mService.acknowledgeDeviceCompliant();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner of an organization-owned managed profile to query whether it needs
+ * to acknowledge device compliance to allow the user to turn the profile off if needed
+ * according to the maximum profile time off policy.
+ *
+ * Normally when acknowledgement is needed the DPC gets a
+ * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)} callback.
+ * But if the callback was not delivered or handled for some reason, this method can be used to
+ * verify if acknowledgement is needed.
+ *
+ * @throws IllegalStateException if the user isn't unlocked
+ * @see #acknowledgeDeviceCompliant()
+ * @see #setManagedProfileMaximumTimeOff(ComponentName, long)
+ * @see DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)
+ */
+ public boolean isComplianceAcknowledgementRequired() {
+ throwIfParentInstance("isComplianceAcknowledgementRequired");
+ if (mService != null) {
+ try {
+ return mService.isComplianceAcknowledgementRequired();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns {@code true} when {@code userId} has a profile owner that is capable of resetting
* password in RUNNING_LOCKED state. For that it should have at least one direct boot aware
* component and have an active password reset token. Can only be called by the system.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0ad92b7..5e49a98 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -495,6 +495,10 @@
long getManagedProfileMaximumTimeOff(in ComponentName admin);
void setManagedProfileMaximumTimeOff(in ComponentName admin, long timeoutMs);
+
+ void acknowledgeDeviceCompliant();
+ boolean isComplianceAcknowledgementRequired();
+
boolean canProfileOwnerResetPasswordWhenLocked(int userId);
void setNextOperationSafety(int operation, int reason);
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index ab38832..74e1ece 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -104,16 +104,15 @@
*
* @param packageName The package name of the app in question.
* @param overrides A map from changeId to the override applied for this change id.
- * @hide
*/
- @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG)
- public static void setPackageOverride(String packageName,
- Map<Long, PackageOverride> overrides) {
+ @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
+ public static void setPackageOverride(@NonNull String packageName,
+ @NonNull Map<Long, PackageOverride> overrides) {
IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
try {
- platformCompat.setOverridesFromInstaller(config, packageName);
+ platformCompat.setOverridesOnReleaseBuilds(config, packageName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java
index 9f97cd4..59b3555 100644
--- a/core/java/android/app/compat/PackageOverride.java
+++ b/core/java/android/app/compat/PackageOverride.java
@@ -17,8 +17,9 @@
package android.app.compat;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
-import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -32,15 +33,16 @@
*
* @hide
*/
-public class PackageOverride implements Parcelable {
+@SystemApi
+public final class PackageOverride {
+ /** @hide */
@IntDef({
VALUE_UNDEFINED,
VALUE_ENABLED,
VALUE_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
- /** @hide */
public @interface EvaluatedOverride {
}
@@ -75,10 +77,6 @@
this.mEnabled = enabled;
}
- private PackageOverride(Parcel in) {
- this(in.readLong(), in.readLong(), in.readBoolean());
- }
-
/**
* Evaluate the override for the given {@code versionCode}. If no override is defined for
* the specified version code, {@link #VALUE_UNDEFINED} is returned.
@@ -114,25 +112,23 @@
}
/** Returns the enabled value for the override. */
- public boolean getEnabled() {
+ public boolean isEnabled() {
return mEnabled;
}
/** @hide */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** @hide */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(Parcel dest) {
dest.writeLong(mMinVersionCode);
dest.writeLong(mMaxVersionCode);
dest.writeBoolean(mEnabled);
}
/** @hide */
+ public static PackageOverride createFromParcel(Parcel in) {
+ return new PackageOverride(in.readLong(), in.readLong(), in.readBoolean());
+ }
+
+ /** @hide */
@Override
public String toString() {
if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
@@ -141,25 +137,10 @@
return String.format("[%d,%d,%b]", mMinVersionCode, mMaxVersionCode, mEnabled);
}
- /** @hide */
- public static final Creator<PackageOverride> CREATOR =
- new Creator<PackageOverride>() {
-
- @Override
- public PackageOverride createFromParcel(Parcel in) {
- return new PackageOverride(in);
- }
-
- @Override
- public PackageOverride[] newArray(int size) {
- return new PackageOverride[size];
- }
- };
-
/**
* Builder to construct a PackageOverride.
*/
- public static class Builder {
+ public static final class Builder {
private long mMinVersionCode = Long.MIN_VALUE;
private long mMaxVersionCode = Long.MAX_VALUE;
private boolean mEnabled;
@@ -169,6 +150,7 @@
*
* default value: {@code Long.MIN_VALUE}.
*/
+ @NonNull
public Builder setMinVersionCode(long minVersionCode) {
mMinVersionCode = minVersionCode;
return this;
@@ -179,6 +161,7 @@
*
* default value: {@code Long.MAX_VALUE}.
*/
+ @NonNull
public Builder setMaxVersionCode(long maxVersionCode) {
mMaxVersionCode = maxVersionCode;
return this;
@@ -189,6 +172,7 @@
*
* default value: {@code false}.
*/
+ @NonNull
public Builder setEnabled(boolean enabled) {
mEnabled = enabled;
return this;
@@ -200,6 +184,7 @@
* @throws IllegalArgumentException if {@code minVersionCode} is larger than
* {@code maxVersionCode}.
*/
+ @NonNull
public PackageOverride build() {
if (mMinVersionCode > mMaxVersionCode) {
throw new IllegalArgumentException("minVersionCode must not be larger than "
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 4fb5577..632572d 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -567,6 +567,7 @@
* @return true if priority is set, false on error
* @hide
* @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
+ * @removed
*/
@Deprecated
@SystemApi
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 86bd8a2..2ce7156 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -425,6 +425,32 @@
mContext.getPackageName(), deviceAddress);
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
+ }
+ }
+
+ /**
+ * Associates given device with given app for the given user directly, without UI prompt.
+ *
+ * @return whether successful
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES)
+ public boolean associate(
+ @NonNull String packageName,
+ @NonNull MacAddress macAddress) {
+ if (!checkFeaturePresent()) {
+ return false;
+ }
+ Objects.requireNonNull(packageName, "package name cannot be null");
+ Objects.requireNonNull(macAddress, "mac address cannot be null");
+
+ UserHandle user = android.os.Process.myUserHandle();
+ try {
+ return mService.createAssociation(
+ packageName, macAddress.toString(), user.getIdentifier());
+ } catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 95d3515..83db358 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -51,4 +51,6 @@
void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress);
boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
+
+ boolean createAssociation(in String packageName, in String macAddress, int userId);
}
diff --git a/core/java/android/content/pm/AppSearchPerson.java b/core/java/android/content/pm/AppSearchPerson.java
index 66295eb..9283e5f 100644
--- a/core/java/android/content/pm/AppSearchPerson.java
+++ b/core/java/android/content/pm/AppSearchPerson.java
@@ -107,7 +107,7 @@
public static class Builder extends GenericDocument.Builder<Builder> {
public Builder(@NonNull final String id) {
- super(id, SCHEMA_TYPE);
+ super(/*namespace=*/ "", id, SCHEMA_TYPE);
}
/** @hide */
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index c04d3be..b2478ca 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -217,9 +217,8 @@
@NonNull
public static AppSearchShortcutInfo instance(@NonNull final ShortcutInfo shortcutInfo) {
Objects.requireNonNull(shortcutInfo);
- return new Builder(shortcutInfo.getId())
+ return new Builder(shortcutInfo.getPackage(), shortcutInfo.getId())
.setActivity(shortcutInfo.getActivity())
- .setNamespace(shortcutInfo.getPackage())
.setShortLabel(shortcutInfo.getShortLabel())
.setShortLabelResId(shortcutInfo.getShortLabelResourceId())
.setShortLabelResName(shortcutInfo.getTitleResName())
@@ -345,8 +344,8 @@
@VisibleForTesting
public static class Builder extends GenericDocument.Builder<Builder> {
- public Builder(String id) {
- super(id, SCHEMA_TYPE);
+ public Builder(String packageName, String id) {
+ super(/*namespace=*/ packageName, id, SCHEMA_TYPE);
}
/**
@@ -574,16 +573,6 @@
/**
* @hide
*/
- public Builder setPackageName(@Nullable final String packageName) {
- if (!TextUtils.isEmpty(packageName)) {
- setNamespace(packageName);
- }
- return this;
- }
-
- /**
- * @hide
- */
public Builder setFlags(@ShortcutInfo.ShortcutFlags final int flags) {
setPropertyLong(KEY_FLAGS, flattenFlags(flags));
return this;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 0da453d..5f3ec36 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1363,7 +1363,7 @@
* Indicates if the application has requested GWP-ASan to be enabled, disabled, or left
* unspecified. Processes can override this setting.
*/
- private @GwpAsanMode int gwpAsanMode;
+ private @GwpAsanMode int gwpAsanMode = GWP_ASAN_DEFAULT;
/**
* Default (unspecified) setting of Memtag.
@@ -1402,13 +1402,38 @@
* Indicates if the application has requested Memtag to be enabled, disabled, or left
* unspecified. Processes can override this setting.
*/
- private @MemtagMode int memtagMode;
+ private @MemtagMode int memtagMode = MEMTAG_DEFAULT;
+
+ /**
+ * Default (unspecified) setting of nativeHeapZeroInitialized.
+ */
+ public static final int ZEROINIT_DEFAULT = -1;
+
+ /**
+ * Disable zero-initialization of the native heap in this application or process.
+ */
+ public static final int ZEROINIT_DISABLED = 0;
+
+ /**
+ * Enable zero-initialization of the native heap in this application or process.
+ */
+ public static final int ZEROINIT_ENABLED = 1;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ZEROINIT_"}, value = {
+ ZEROINIT_DEFAULT,
+ ZEROINIT_DISABLED,
+ ZEROINIT_ENABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NativeHeapZeroInitialized {}
/**
* Enable automatic zero-initialization of native heap memory allocations.
*/
- @Nullable
- private Boolean nativeHeapZeroInit;
+ private @NativeHeapZeroInitialized int nativeHeapZeroInitialized = ZEROINIT_DEFAULT;
/**
* If {@code true} this app requests optimized external storage access.
@@ -1570,8 +1595,8 @@
if (memtagMode != MEMTAG_DEFAULT) {
pw.println(prefix + "memtagMode=" + memtagMode);
}
- if (nativeHeapZeroInit != null) {
- pw.println(prefix + "nativeHeapZeroInit=" + nativeHeapZeroInit);
+ if (nativeHeapZeroInitialized != ZEROINIT_DEFAULT) {
+ pw.println(prefix + "nativeHeapZeroInitialized=" + nativeHeapZeroInitialized);
}
if (requestOptimizedExternalStorageAccess != null) {
pw.println(prefix + "requestOptimizedExternalStorageAccess="
@@ -1686,8 +1711,9 @@
if (memtagMode != MEMTAG_DEFAULT) {
proto.write(ApplicationInfoProto.Detail.ENABLE_MEMTAG, memtagMode);
}
- if (nativeHeapZeroInit != null) {
- proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT, nativeHeapZeroInit);
+ if (nativeHeapZeroInitialized != ZEROINIT_DEFAULT) {
+ proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT,
+ nativeHeapZeroInitialized);
}
proto.end(detailToken);
}
@@ -1802,7 +1828,7 @@
zygotePreloadName = orig.zygotePreloadName;
gwpAsanMode = orig.gwpAsanMode;
memtagMode = orig.memtagMode;
- nativeHeapZeroInit = orig.nativeHeapZeroInit;
+ nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
requestOptimizedExternalStorageAccess = orig.requestOptimizedExternalStorageAccess;
}
@@ -1891,7 +1917,7 @@
dest.writeString8(zygotePreloadName);
dest.writeInt(gwpAsanMode);
dest.writeInt(memtagMode);
- sForBoolean.parcel(nativeHeapZeroInit, dest, parcelableFlags);
+ dest.writeInt(nativeHeapZeroInitialized);
sForBoolean.parcel(requestOptimizedExternalStorageAccess, dest, parcelableFlags);
}
@@ -1977,7 +2003,7 @@
zygotePreloadName = source.readString8();
gwpAsanMode = source.readInt();
memtagMode = source.readInt();
- nativeHeapZeroInit = sForBoolean.unparcel(source);
+ nativeHeapZeroInitialized = source.readInt();
requestOptimizedExternalStorageAccess = sForBoolean.unparcel(source);
}
@@ -2382,7 +2408,9 @@
/** {@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} */ public void setNativeHeapZeroInitialized(@NativeHeapZeroInitialized int value) {
+ nativeHeapZeroInitialized = value;
+ }
/** {@hide} */
public void setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) {
requestOptimizedExternalStorageAccess = value;
@@ -2400,8 +2428,22 @@
/** {@hide} */ public String[] getSplitResourcePaths() { return splitPublicSourceDirs; }
@GwpAsanMode
public int getGwpAsanMode() { return gwpAsanMode; }
+
+ /**
+ * Returns whether the application has requested Memtag to be enabled, disabled, or left
+ * unspecified. Processes can override this setting.
+ */
@MemtagMode
- public int getMemtagMode() { return memtagMode; }
- @Nullable
- public Boolean isNativeHeapZeroInit() { return nativeHeapZeroInit; }
+ public int getMemtagMode() {
+ return memtagMode;
+ }
+
+ /**
+ * Returns whether the application has requested automatic zero-initialization of native heap
+ * memory allocations to be enabled or disabled.
+ */
+ @NativeHeapZeroInitialized
+ public int getNativeHeapZeroInitialized() {
+ return nativeHeapZeroInitialized;
+ }
}
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
index 3dd5ee1..632c0f5 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -62,8 +62,7 @@
/**
* Enable automatic zero-initialization of native heap memory allocations.
*/
- @Nullable
- public Boolean nativeHeapZeroInit;
+ public @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized;
@Deprecated
public ProcessInfo(@NonNull ProcessInfo orig) {
@@ -71,7 +70,7 @@
this.deniedPermissions = orig.deniedPermissions;
this.gwpAsanMode = orig.gwpAsanMode;
this.memtagMode = orig.memtagMode;
- this.nativeHeapZeroInit = orig.nativeHeapZeroInit;
+ this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
}
@@ -101,7 +100,7 @@
* @param memtagMode
* Indicates if the process has requested Memtag to be enabled (in sync or async mode),
* disabled, or left unspecified.
- * @param nativeHeapZeroInit
+ * @param nativeHeapZeroInitialized
* Enable automatic zero-initialization of native heap memory allocations.
*/
@DataClass.Generated.Member
@@ -110,7 +109,7 @@
@Nullable ArraySet<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
- @Nullable Boolean nativeHeapZeroInit) {
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -121,7 +120,9 @@
this.memtagMode = memtagMode;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.MemtagMode.class, null, memtagMode);
- this.nativeHeapZeroInit = nativeHeapZeroInit;
+ this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
// onConstructed(); // You can define this method to get a callback
}
@@ -145,13 +146,12 @@
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);
+ dest.writeInt(nativeHeapZeroInitialized);
}
@Override
@@ -170,7 +170,7 @@
ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
int _gwpAsanMode = in.readInt();
int _memtagMode = in.readInt();
- Boolean _nativeHeapZeroInit = (flg & 0x10) == 0 ? null : (Boolean) in.readBoolean();
+ int _nativeHeapZeroInitialized = in.readInt();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -182,7 +182,9 @@
this.memtagMode = _memtagMode;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.MemtagMode.class, null, memtagMode);
- this.nativeHeapZeroInit = _nativeHeapZeroInit;
+ this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
// onConstructed(); // You can define this method to get a callback
}
@@ -202,10 +204,10 @@
};
@DataClass.Generated(
- time = 1611614699049L,
+ time = 1615850184524L,
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\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)")
+ 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.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\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/SuspendDialogInfo.java b/core/java/android/content/pm/SuspendDialogInfo.java
index 60f3218..23945ee 100644
--- a/core/java/android/content/pm/SuspendDialogInfo.java
+++ b/core/java/android/content/pm/SuspendDialogInfo.java
@@ -65,16 +65,20 @@
private static final String TAG = SuspendDialogInfo.class.getSimpleName();
private static final String XML_ATTR_ICON_RES_ID = "iconResId";
private static final String XML_ATTR_TITLE_RES_ID = "titleResId";
+ private static final String XML_ATTR_TITLE = "title";
private static final String XML_ATTR_DIALOG_MESSAGE_RES_ID = "dialogMessageResId";
private static final String XML_ATTR_DIALOG_MESSAGE = "dialogMessage";
private static final String XML_ATTR_BUTTON_TEXT_RES_ID = "buttonTextResId";
+ private static final String XML_ATTR_BUTTON_TEXT = "buttonText";
private static final String XML_ATTR_BUTTON_ACTION = "buttonAction";
private final int mIconResId;
private final int mTitleResId;
+ private final String mTitle;
private final int mDialogMessageResId;
private final String mDialogMessage;
private final int mNeutralButtonTextResId;
+ private final String mNeutralButtonText;
private final int mNeutralButtonAction;
/**
@@ -129,6 +133,16 @@
}
/**
+ * @return the title to be shown on the dialog. Returns {@code null} if {@link #getTitleResId()}
+ * returns a valid resource id
+ * @hide
+ */
+ @Nullable
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
* @return the resource id of the text to be shown in the dialog's body
* @hide
*/
@@ -148,7 +162,7 @@
}
/**
- * @return the text to be shown
+ * @return the text to be shown on the neutral button
* @hide
*/
@StringRes
@@ -157,6 +171,16 @@
}
/**
+ * @return the text to be shown on the neutral button. Returns {@code null} if
+ * {@link #getNeutralButtonTextResId()} returns a valid resource id
+ * @hide
+ */
+ @Nullable
+ public String getNeutralButtonText() {
+ return mNeutralButtonText;
+ }
+
+ /**
* @return The {@link ButtonAction} that happens on tapping this button
* @hide
*/
@@ -174,6 +198,8 @@
}
if (mTitleResId != ID_NULL) {
out.attributeInt(null, XML_ATTR_TITLE_RES_ID, mTitleResId);
+ } else {
+ XmlUtils.writeStringAttribute(out, XML_ATTR_TITLE, mTitle);
}
if (mDialogMessageResId != ID_NULL) {
out.attributeInt(null, XML_ATTR_DIALOG_MESSAGE_RES_ID, mDialogMessageResId);
@@ -182,6 +208,8 @@
}
if (mNeutralButtonTextResId != ID_NULL) {
out.attributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, mNeutralButtonTextResId);
+ } else {
+ XmlUtils.writeStringAttribute(out, XML_ATTR_BUTTON_TEXT, mNeutralButtonText);
}
out.attributeInt(null, XML_ATTR_BUTTON_ACTION, mNeutralButtonAction);
}
@@ -194,8 +222,10 @@
try {
final int iconId = in.getAttributeInt(null, XML_ATTR_ICON_RES_ID, ID_NULL);
final int titleId = in.getAttributeInt(null, XML_ATTR_TITLE_RES_ID, ID_NULL);
+ final String title = XmlUtils.readStringAttribute(in, XML_ATTR_TITLE);
final int buttonTextId =
in.getAttributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, ID_NULL);
+ final String buttonText = XmlUtils.readStringAttribute(in, XML_ATTR_BUTTON_TEXT);
final int buttonAction =
in.getAttributeInt(null, XML_ATTR_BUTTON_ACTION, BUTTON_ACTION_MORE_DETAILS);
final int dialogMessageResId =
@@ -207,9 +237,13 @@
}
if (titleId != ID_NULL) {
dialogInfoBuilder.setTitle(titleId);
+ } else if (title != null) {
+ dialogInfoBuilder.setTitle(title);
}
if (buttonTextId != ID_NULL) {
dialogInfoBuilder.setNeutralButtonText(buttonTextId);
+ } else if (buttonText != null) {
+ dialogInfoBuilder.setNeutralButtonText(buttonText);
}
if (dialogMessageResId != ID_NULL) {
dialogInfoBuilder.setMessage(dialogMessageResId);
@@ -227,7 +261,9 @@
public int hashCode() {
int hashCode = mIconResId;
hashCode = 31 * hashCode + mTitleResId;
+ hashCode = 31 * hashCode + Objects.hashCode(mTitle);
hashCode = 31 * hashCode + mNeutralButtonTextResId;
+ hashCode = 31 * hashCode + Objects.hashCode(mNeutralButtonText);
hashCode = 31 * hashCode + mDialogMessageResId;
hashCode = 31 * hashCode + Objects.hashCode(mDialogMessage);
hashCode = 31 * hashCode + mNeutralButtonAction;
@@ -245,10 +281,12 @@
final SuspendDialogInfo otherDialogInfo = (SuspendDialogInfo) obj;
return mIconResId == otherDialogInfo.mIconResId
&& mTitleResId == otherDialogInfo.mTitleResId
+ && Objects.equals(mTitle, otherDialogInfo.mTitle)
&& mDialogMessageResId == otherDialogInfo.mDialogMessageResId
+ && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage)
&& mNeutralButtonTextResId == otherDialogInfo.mNeutralButtonTextResId
- && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction
- && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage);
+ && Objects.equals(mNeutralButtonText, otherDialogInfo.mNeutralButtonText)
+ && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction;
}
@NonNull
@@ -264,11 +302,19 @@
builder.append("mTitleResId = 0x");
builder.append(Integer.toHexString(mTitleResId));
builder.append(" ");
+ } else if (mTitle != null) {
+ builder.append("mTitle = \"");
+ builder.append(mTitle);
+ builder.append("\"");
}
if (mNeutralButtonTextResId != ID_NULL) {
builder.append("mNeutralButtonTextResId = 0x");
builder.append(Integer.toHexString(mNeutralButtonTextResId));
builder.append(" ");
+ } else if (mNeutralButtonText != null) {
+ builder.append("mNeutralButtonText = \"");
+ builder.append(mNeutralButtonText);
+ builder.append("\"");
}
if (mDialogMessageResId != ID_NULL) {
builder.append("mDialogMessageResId = 0x");
@@ -294,27 +340,33 @@
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mIconResId);
dest.writeInt(mTitleResId);
+ dest.writeString(mTitle);
dest.writeInt(mDialogMessageResId);
dest.writeString(mDialogMessage);
dest.writeInt(mNeutralButtonTextResId);
+ dest.writeString(mNeutralButtonText);
dest.writeInt(mNeutralButtonAction);
}
private SuspendDialogInfo(Parcel source) {
mIconResId = source.readInt();
mTitleResId = source.readInt();
+ mTitle = source.readString();
mDialogMessageResId = source.readInt();
mDialogMessage = source.readString();
mNeutralButtonTextResId = source.readInt();
+ mNeutralButtonText = source.readString();
mNeutralButtonAction = source.readInt();
}
SuspendDialogInfo(Builder b) {
mIconResId = b.mIconResId;
mTitleResId = b.mTitleResId;
+ mTitle = (mTitleResId == ID_NULL) ? b.mTitle : null;
mDialogMessageResId = b.mDialogMessageResId;
mDialogMessage = (mDialogMessageResId == ID_NULL) ? b.mDialogMessage : null;
mNeutralButtonTextResId = b.mNeutralButtonTextResId;
+ mNeutralButtonText = (mNeutralButtonTextResId == ID_NULL) ? b.mNeutralButtonText : null;
mNeutralButtonAction = b.mNeutralButtonAction;
}
@@ -338,8 +390,10 @@
private int mDialogMessageResId = ID_NULL;
private String mDialogMessage;
private int mTitleResId = ID_NULL;
+ private String mTitle;
private int mIconResId = ID_NULL;
private int mNeutralButtonTextResId = ID_NULL;
+ private String mNeutralButtonText;
private int mNeutralButtonAction = BUTTON_ACTION_MORE_DETAILS;
/**
@@ -370,6 +424,21 @@
}
/**
+ * Set the title text of the dialog. Ignored if a resource id is set via
+ * {@link #setTitle(int)}
+ *
+ * @param title The title of the dialog.
+ * @return this builder object.
+ * @see #setTitle(int)
+ */
+ @NonNull
+ public Builder setTitle(@NonNull String title) {
+ Preconditions.checkStringNotEmpty(title, "Title cannot be null or empty");
+ mTitle = title;
+ return this;
+ }
+
+ /**
* Set the text to show in the body of the dialog. Ignored if a resource id is set via
* {@link #setMessage(int)}.
* <p>
@@ -427,6 +496,22 @@
}
/**
+ * Set the text to be shown on the neutral button. Ignored if a resource id is set via
+ * {@link #setNeutralButtonText(int)}
+ *
+ * @param neutralButtonText The title of the dialog.
+ * @return this builder object.
+ * @see #setNeutralButtonText(int)
+ */
+ @NonNull
+ public Builder setNeutralButtonText(@NonNull String neutralButtonText) {
+ Preconditions.checkStringNotEmpty(neutralButtonText,
+ "Button text cannot be null or empty");
+ mNeutralButtonText = neutralButtonText;
+ return this;
+ }
+
+ /**
* Set the action expected to happen on neutral button tap. Defaults to
* {@link #BUTTON_ACTION_MORE_DETAILS} if this is not provided.
*
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 4dc9ce8..1c65e00 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.FeatureGroupInfo;
import android.content.pm.FeatureInfo;
@@ -251,11 +252,12 @@
ParsingPackage setEnabled(boolean enabled);
- ParsingPackage setGwpAsanMode(int gwpAsanMode);
+ ParsingPackage setGwpAsanMode(@ApplicationInfo.GwpAsanMode int gwpAsanMode);
- ParsingPackage setMemtagMode(int memtagMode);
+ ParsingPackage setMemtagMode(@ApplicationInfo.MemtagMode int memtagMode);
- ParsingPackage setNativeHeapZeroInit(@Nullable Boolean nativeHeapZeroInit);
+ ParsingPackage setNativeHeapZeroInitialized(
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized);
ParsingPackage setRequestOptimizedExternalStorageAccess(
@Nullable Boolean requestOptimizedExternalStorageAccess);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 065ed2e..60aac76 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -382,12 +382,14 @@
private int autoRevokePermissions;
- protected int gwpAsanMode;
- protected int memtagMode;
+ @ApplicationInfo.GwpAsanMode
+ private int gwpAsanMode;
- @Nullable
- @DataClass.ParcelWith(ForBoolean.class)
- private Boolean nativeHeapZeroInit;
+ @ApplicationInfo.MemtagMode
+ private int memtagMode;
+
+ @ApplicationInfo.NativeHeapZeroInitialized
+ private int nativeHeapZeroInitialized;
@Nullable
@DataClass.ParcelWith(ForBoolean.class)
@@ -1071,7 +1073,7 @@
appInfo.zygotePreloadName = zygotePreloadName;
appInfo.setGwpAsanMode(gwpAsanMode);
appInfo.setMemtagMode(memtagMode);
- appInfo.setNativeHeapZeroInit(nativeHeapZeroInit);
+ appInfo.setNativeHeapZeroInitialized(nativeHeapZeroInitialized);
appInfo.setRequestOptimizedExternalStorageAccess(requestOptimizedExternalStorageAccess);
appInfo.setBaseCodePath(mBaseApkPath);
appInfo.setBaseResourcePath(mBaseApkPath);
@@ -1207,7 +1209,7 @@
dest.writeLong(this.mBooleans);
dest.writeMap(this.mProperties);
dest.writeInt(this.memtagMode);
- sForBoolean.parcel(this.nativeHeapZeroInit, dest, flags);
+ dest.writeInt(this.nativeHeapZeroInitialized);
sForBoolean.parcel(this.requestOptimizedExternalStorageAccess, dest, flags);
}
@@ -1331,7 +1333,7 @@
this.mBooleans = in.readLong();
this.mProperties = in.createTypedArrayMap(Property.CREATOR);
this.memtagMode = in.readInt();
- this.nativeHeapZeroInit = sForBoolean.unparcel(in);
+ this.nativeHeapZeroInitialized = in.readInt();
this.requestOptimizedExternalStorageAccess = sForBoolean.unparcel(in);
assignDerivedFields();
}
@@ -2096,20 +2098,22 @@
return getBoolean(Booleans.DIRECT_BOOT_AWARE);
}
+ @ApplicationInfo.GwpAsanMode
@Override
public int getGwpAsanMode() {
return gwpAsanMode;
}
+ @ApplicationInfo.MemtagMode
@Override
public int getMemtagMode() {
return memtagMode;
}
- @Nullable
+ @ApplicationInfo.NativeHeapZeroInitialized
@Override
- public Boolean isNativeHeapZeroInit() {
- return nativeHeapZeroInit;
+ public int getNativeHeapZeroInitialized() {
+ return nativeHeapZeroInitialized;
}
@Nullable
@@ -2550,20 +2554,21 @@
}
@Override
- public ParsingPackageImpl setGwpAsanMode(int value) {
+ public ParsingPackageImpl setGwpAsanMode(@ApplicationInfo.GwpAsanMode int value) {
gwpAsanMode = value;
return this;
}
@Override
- public ParsingPackageImpl setMemtagMode(int value) {
+ public ParsingPackageImpl setMemtagMode(@ApplicationInfo.MemtagMode int value) {
memtagMode = value;
return this;
}
@Override
- public ParsingPackageImpl setNativeHeapZeroInit(@Nullable Boolean value) {
- nativeHeapZeroInit = value;
+ public ParsingPackageImpl setNativeHeapZeroInitialized(
+ @ApplicationInfo.NativeHeapZeroInitialized int value) {
+ nativeHeapZeroInitialized = value;
return this;
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 47dfa9d..cfd828e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -887,20 +887,22 @@
* @see ApplicationInfo#gwpAsanMode
* @see R.styleable#AndroidManifest_gwpAsanMode
*/
+ @ApplicationInfo.GwpAsanMode
int getGwpAsanMode();
/**
* @see ApplicationInfo#memtagMode
* @see R.styleable#AndroidManifest_memtagMode
*/
+ @ApplicationInfo.MemtagMode
int getMemtagMode();
- /**
- * @see ApplicationInfo#nativeHeapZeroInit
- * @see R.styleable#AndroidManifest_nativeHeapZeroInit
+ /**
+ * @see ApplicationInfo#nativeHeapZeroInitialized
+ * @see R.styleable#AndroidManifest_nativeHeapZeroInitialized
*/
- @Nullable
- Boolean isNativeHeapZeroInit();
+ @ApplicationInfo.NativeHeapZeroInitialized
+ int getNativeHeapZeroInitialized();
@Nullable
Boolean hasRequestOptimizedExternalStorageAccess();
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 0e1574c..9f69d0c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2008,9 +2008,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));
+ if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized)) {
+ Boolean v = sa.getBoolean(
+ R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized, false);
+ pkg.setNativeHeapZeroInitialized(
+ v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED);
}
if (sa.hasValue(
R.styleable.AndroidManifestApplication_requestOptimizedExternalStorageAccess)) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index 89fef9d..54a60d3 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -42,10 +42,12 @@
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
protected Set<String> deniedPermissions = emptySet();
+ @ApplicationInfo.GwpAsanMode
protected int gwpAsanMode = ApplicationInfo.GWP_ASAN_DEFAULT;
+ @ApplicationInfo.MemtagMode
protected int memtagMode = ApplicationInfo.MEMTAG_DEFAULT;
- @Nullable
- protected Boolean nativeHeapZeroInit = null;
+ @ApplicationInfo.NativeHeapZeroInitialized
+ protected int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
public ParsedProcess() {
}
@@ -78,9 +80,9 @@
public ParsedProcess(
@NonNull String name,
@NonNull Set<String> deniedPermissions,
- int gwpAsanMode,
- int memtagMode,
- @Nullable Boolean nativeHeapZeroInit) {
+ @ApplicationInfo.GwpAsanMode int gwpAsanMode,
+ @ApplicationInfo.MemtagMode int memtagMode,
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -88,8 +90,14 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deniedPermissions);
this.gwpAsanMode = gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
this.memtagMode = memtagMode;
- this.nativeHeapZeroInit = nativeHeapZeroInit;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
// onConstructed(); // You can define this method to get a callback
}
@@ -105,18 +113,18 @@
}
@DataClass.Generated.Member
- public int getGwpAsanMode() {
+ public @ApplicationInfo.GwpAsanMode int getGwpAsanMode() {
return gwpAsanMode;
}
@DataClass.Generated.Member
- public int getMemtagMode() {
+ public @ApplicationInfo.MemtagMode int getMemtagMode() {
return memtagMode;
}
@DataClass.Generated.Member
- public @Nullable Boolean getNativeHeapZeroInit() {
- return nativeHeapZeroInit;
+ public @ApplicationInfo.NativeHeapZeroInitialized int getNativeHeapZeroInitialized() {
+ return nativeHeapZeroInitialized;
}
@DataClass.Generated.Member
@@ -136,14 +144,11 @@
// 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);
+ dest.writeInt(nativeHeapZeroInitialized);
}
@Override
@@ -157,12 +162,11 @@
// 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();
+ int _nativeHeapZeroInitialized = in.readInt();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -171,8 +175,14 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deniedPermissions);
this.gwpAsanMode = _gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
this.memtagMode = _memtagMode;
- this.nativeHeapZeroInit = _nativeHeapZeroInit;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
// onConstructed(); // You can define this method to get a callback
}
@@ -192,10 +202,10 @@
};
@DataClass.Generated(
- time = 1611615591258L,
+ time = 1615850515058L,
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\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)")
+ 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 @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprotected @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprotected @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\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 2579774..e417e74 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -17,6 +17,7 @@
package android.content.pm.parsing.component;
import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
@@ -104,9 +105,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);
+ if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized)) {
+ Boolean v = sa.getBoolean(
+ R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized, false);
+ proc.nativeHeapZeroInitialized =
+ v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED;
}
} finally {
sa.recycle();
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index d2d1441..33920c6 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -179,10 +179,7 @@
*/
@SystemApi
@Nullable
- @RequiresPermission(anyOf = {
- android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
- android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
- })
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
throws NameNotFoundException {
try {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 5eaa766..2a349e9 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -46,7 +46,7 @@
* The indices used to retrieve values from this structure correspond to
* the positions of the attributes given to obtainStyledAttributes.
*/
-public class TypedArray {
+public class TypedArray implements AutoCloseable {
static TypedArray obtain(Resources res, int len) {
TypedArray attrs = res.mTypedArrayPool.acquire();
@@ -1253,6 +1253,17 @@
}
/**
+ * Recycles the TypedArray, to be re-used by a later caller. After calling
+ * this function you must not ever touch the typed array again.
+ *
+ * @see #recycle()
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public void close() {
+ recycle();
+ }
+
+ /**
* Extracts theme attributes from a typed array for later resolution using
* {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}.
* Removes the entries from the typed array so that subsequent calls to typed
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 07ebbaf..6654c2c 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1210,6 +1210,25 @@
new Key<android.util.Range<Float>>("android.control.zoomRatioRange", new TypeReference<android.util.Range<Float>>() {{ }});
/**
+ * <p>List of available high speed video size, fps range and max batch size configurations
+ * supported by the camera device, in the format of
+ * (width, height, fps_min, fps_max, batch_size_max),
+ * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.control.availableHighSpeedVideoConfigurations, for configurations
+ * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Range of valid values:</b><br></p>
+ * <p>For each configuration, the fps_max >= 120fps.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.HighSpeedVideoConfiguration[]> CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.HighSpeedVideoConfiguration[]>("android.control.availableHighSpeedVideoConfigurationsMaximumResolution", android.hardware.camera2.params.HighSpeedVideoConfiguration[].class);
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
@@ -1770,6 +1789,48 @@
new Key<float[]>("android.lens.distortion", float[].class);
/**
+ * <p>The correction coefficients to correct for this camera device's
+ * radial and tangential lens distortion for a
+ * CaptureRequest with {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Units</b>:
+ * Unitless coefficients.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+ *
+ * @see CameraCharacteristics#LENS_DISTORTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<float[]> LENS_DISTORTION_MAXIMUM_RESOLUTION =
+ new Key<float[]>("android.lens.distortionMaximumResolution", float[].class);
+
+ /**
+ * <p>The parameters for this camera device's intrinsic
+ * calibration when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Units</b>:
+ * Pixels in the
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution}
+ * coordinate system.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+ *
+ * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<float[]> LENS_INTRINSIC_CALIBRATION_MAXIMUM_RESOLUTION =
+ new Key<float[]>("android.lens.intrinsicCalibrationMaximumResolution", float[].class);
+
+ /**
* <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported
* by this camera device.</p>
* <p>Full-capability camera devices will always support OFF and FAST.</p>
@@ -2056,6 +2117,8 @@
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR ULTRA_HIGH_RESOLUTION_SENSOR}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING REMOSAIC_REPROCESSING}</li>
* </ul>
*
* <p>This key is available on all devices.</p>
@@ -2077,6 +2140,8 @@
* @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA
* @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA
* @see #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING
*/
@PublicKey
@NonNull
@@ -2535,8 +2600,6 @@
* set to either OFF or FAST.</p>
* <p>When multiple streams are used in a request, the minimum frame
* duration will be max(individual stream min durations).</p>
- * <p>The minimum frame duration of a stream (of a particular format, size)
- * is the same regardless of whether the stream is input or output.</p>
* <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
* android.scaler.availableStallDurations for more details about
* calculating the max frame rate.</p>
@@ -2916,10 +2979,10 @@
* configurations which belong to this physical camera, and it will advertise and will only
* advertise the maximum supported resolutions for a particular format.</p>
* <p>If this camera device isn't a physical camera device constituting a logical camera,
- * but a standalone ULTRA_HIGH_RESOLUTION_SENSOR camera, this field represents the
- * multi-resolution input/output stream configurations of default mode and max resolution
- * modes. The sizes will be the maximum resolution of a particular format for default mode
- * and max resolution mode.</p>
+ * but a standalone {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * camera, this field represents the multi-resolution input/output stream configurations of
+ * default mode and max resolution modes. The sizes will be the maximum resolution of a
+ * particular format for default mode and max resolution mode.</p>
* <p>This field will only be advertised if the device is a physical camera of a
* logical multi-camera device or an ultra high resolution sensor camera. For a logical
* multi-camera, the camera API will derive the logical camera’s multi-resolution stream
@@ -2977,6 +3040,132 @@
new Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap>("android.scaler.multiResolutionStreamConfigurationMap", android.hardware.camera2.params.MultiResolutionStreamConfigurationMap.class);
/**
+ * <p>The available stream configurations that this
+ * camera device supports (i.e. format, width, height, output/input stream) for a
+ * CaptureRequest with {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.scaler.availableStreamConfigurations, for configurations
+ * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Not all output formats may be supported in a configuration with
+ * an input stream of a particular format. For more details, see
+ * android.scaler.availableInputOutputFormatsMapMaximumResolution.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.scaler.availableStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination when the camera device is sent a CaptureRequest with
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.scaler.availableMinFrameDurations, for configurations
+ * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>When multiple streams are used in a request (if supported, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}
+ * is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }), the
+ * minimum frame duration will be max(individual stream min durations).</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * android.scaler.availableStallDurationsMaximumResolution for more details about
+ * calculating the max frame rate.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> SCALER_AVAILABLE_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.scaler.availableMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination when CaptureRequests are submitted with
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }</p>
+ * <p>Analogous to android.scaler.availableMinFrameDurations, for configurations
+ * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> SCALER_AVAILABLE_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.scaler.availableStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>The available stream configurations that this
+ * camera device supports when given a CaptureRequest with {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}
+ * set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION };
+ * also includes the minimum frame durations
+ * and the stall durations for each format/size combination.</p>
+ * <p>Analogous to {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ @PublicKey
+ @NonNull
+ @SyntheticKey
+ public static final Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationMap>("android.scaler.streamConfigurationMapMaximumResolution", android.hardware.camera2.params.StreamConfigurationMap.class);
+
+ /**
+ * <p>The mapping of image formats that are supported by this
+ * camera device for input streams, to their corresponding output formats, when
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.scaler.availableInputOutputFormatsMap for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.ReprocessFormatsMap> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.ReprocessFormatsMap>("android.scaler.availableInputOutputFormatsMapMaximumResolution", android.hardware.camera2.params.ReprocessFormatsMap.class);
+
+ /**
+ * <p>An array of mandatory stream combinations which are applicable when
+ * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set
+ * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
+ * This is an app-readable conversion of the maximum resolution mandatory stream combination
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
+ * <p>The array of
+ * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
+ * generated according to the documented
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for each
+ * device which has the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability.
+ * Clients can use the array as a quick reference to find an appropriate camera stream
+ * combination.
+ * The mandatory stream combination array will be {@code null} in case the device is not an
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * device.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ @PublicKey
+ @NonNull
+ @SyntheticKey
+ public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS =
+ new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryMaximumResolutionStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class);
+
+ /**
* <p>The area of the image sensor which corresponds to active pixels after any geometric
* distortion correction has been applied.</p>
* <p>This is the rectangle representing the size of the active region of the sensor (i.e.
@@ -3292,6 +3481,101 @@
new Key<android.graphics.Rect>("android.sensor.info.preCorrectionActiveArraySize", android.graphics.Rect.class);
/**
+ * <p>The area of the image sensor which corresponds to active pixels after any geometric
+ * distortion correction has been applied, when the sensor runs in maximum resolution mode.</p>
+ * <p>Analogous to {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}
+ * is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
+ * Refer to {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} for details, with sensor array related keys
+ * replaced with their
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * counterparts.
+ * This key will only be present for devices which advertise the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability.</p>
+ * <p><b>Units</b>: Pixel coordinates on the image sensor</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION =
+ new Key<android.graphics.Rect>("android.sensor.info.activeArraySizeMaximumResolution", android.graphics.Rect.class);
+
+ /**
+ * <p>Dimensions of the full pixel array, possibly
+ * including black calibration pixels, when the sensor runs in maximum resolution mode.
+ * Analogous to {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is
+ * set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>The pixel count of the full pixel array of the image sensor, which covers
+ * {@link CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE android.sensor.info.physicalSize} area. This represents the full pixel dimensions of
+ * the raw buffers produced by this sensor, when it runs in maximum resolution mode. That
+ * is, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
+ * This key will only be present for devices which advertise the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability.</p>
+ * <p><b>Units</b>: Pixels</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<android.util.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION =
+ new Key<android.util.Size>("android.sensor.info.pixelArraySizeMaximumResolution", android.util.Size.class);
+
+ /**
+ * <p>The area of the image sensor which corresponds to active pixels prior to the
+ * application of any geometric distortion correction, when the sensor runs in maximum
+ * resolution mode. This key must be used for crop / metering regions, only when
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize},
+ * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
+ * This key will only be present for devices which advertise the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability.</p>
+ * <p><b>Units</b>: Pixel coordinates on the image sensor</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<android.graphics.Rect> SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION =
+ new Key<android.graphics.Rect>("android.sensor.info.preCorrectionActiveArraySizeMaximumResolution", android.graphics.Rect.class);
+
+ /**
+ * <p>Dimensions of the group of pixels which are under the same color filter.
+ * This specifies the width and height (pair of integers) of the group of pixels which fall
+ * under the same color filter for ULTRA_HIGH_RESOLUTION sensors.</p>
+ * <p>Sensors can have pixels grouped together under the same color filter in order
+ * to improve various aspects of imaging such as noise reduction, low light
+ * performance etc. These groups can be of various sizes such as 2X2 (quad bayer),
+ * 3X3 (nona-bayer). This key specifies the length and width of the pixels grouped under
+ * the same color filter.</p>
+ * <p>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW images
+ * will have a regular bayer pattern.</p>
+ * <p>This key will not be present for sensors which don't have the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability.</p>
+ * <p><b>Units</b>: Pixels</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<android.util.Size> SENSOR_INFO_BINNING_FACTOR =
+ new Key<android.util.Size>("android.sensor.info.binningFactor", android.util.Size.class);
+
+ /**
* <p>The standard reference illuminant used as the scene light source when
* calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1},
* {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and
@@ -4150,6 +4434,111 @@
new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
/**
+ * <p>The available depth dataspace stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream) when a CaptureRequest is submitted with
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.depth.availableDepthStreamConfigurations, for configurations which
+ * are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDepthStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for depth output formats when a CaptureRequest is submitted with
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.depth.availableDepthMinFrameDurations, for configurations which
+ * are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * android.scaler.availableStallDurationsMaximumResolution for more details about
+ * calculating the max frame rate.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for depth streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.depth.availableDepthStallDurations, for configurations which
+ * are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>The available dynamic depth dataspace stream
+ * configurations that this camera device supports (i.e. format, width, height,
+ * output/input stream) for CaptureRequests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.depth.availableDynamicDepthStreamConfigurations, for configurations
+ * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDynamicDepthStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for dynamic depth output streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.depth.availableDynamicDepthMinFrameDurations, for configurations
+ * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for dynamic depth streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Analogous to android.depth.availableDynamicDepthStallDurations, for configurations
+ * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
* <p>String containing the ids of the underlying physical cameras.</p>
* <p>For a logical camera, this is concatenation of all underlying physical camera IDs.
* The null terminator for physical camera ID must be preserved so that the whole string
@@ -4286,6 +4675,47 @@
public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_STALL_DURATIONS =
new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+ /**
+ * <p>The available HEIC (ISO/IEC 23008-12) stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>Refer to android.heic.availableHeicStreamConfigurations for details.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for HEIC output formats for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.heic.availableHeicMinFrameDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for HEIC streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.heic.availableHeicStallDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index b7b1a14..726bca4 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -507,6 +507,18 @@
return new CameraExtensionCharacteristics(mContext, cameraId, chars);
}
+ private Map<String, CameraCharacteristics> getPhysicalIdToCharsMap(
+ CameraCharacteristics chars) throws CameraAccessException {
+ HashMap<String, CameraCharacteristics> physicalIdsToChars =
+ new HashMap<String, CameraCharacteristics>();
+ Set<String> physicalCameraIds = chars.getPhysicalCameraIds();
+ for (String physicalCameraId : physicalCameraIds) {
+ CameraCharacteristics physicalChars = getCameraCharacteristics(physicalCameraId);
+ physicalIdsToChars.put(physicalCameraId, physicalChars);
+ }
+ return physicalIdsToChars;
+ }
+
/**
* Helper for opening a connection to a camera with the given ID.
*
@@ -535,17 +547,18 @@
throws CameraAccessException {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
-
+ Map<String, CameraCharacteristics> physicalIdsToChars =
+ getPhysicalIdToCharsMap(characteristics);
synchronized (mLock) {
ICameraDeviceUser cameraUser = null;
-
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
new android.hardware.camera2.impl.CameraDeviceImpl(
cameraId,
callback,
executor,
characteristics,
+ physicalIdsToChars,
mContext.getApplicationInfo().targetSdkVersion,
mContext);
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 924dcee..d4da3b9 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -586,7 +586,7 @@
* that is, {@link android.graphics.ImageFormat#PRIVATE } is included in the lists of
* formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li>
* <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
- * returns non empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li>
+ * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li>
* <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.PRIVATE)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.PRIVATE)}</li>
* <li>Using {@link android.graphics.ImageFormat#PRIVATE } does not cause a frame rate drop
* relative to the sensor's maximum capture rate (at that resolution).</li>
@@ -1114,6 +1114,63 @@
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15;
+ /**
+ * <p>This camera device is capable of producing ultra high resolution images in
+ * addition to the image sizes described in the
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.
+ * It can operate in 'default' mode and 'max resolution' mode. It generally does this
+ * by binning pixels in 'default' mode and not binning them in 'max resolution' mode.
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code> describes the streams supported in 'default'
+ * mode.
+ * The stream configurations supported in 'max resolution' mode are described by
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR = 16;
+
+ /**
+ * <p>The device supports reprocessing from the <code>RAW_SENSOR</code> format with a bayer pattern
+ * given by {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor} (m x n group of pixels with the same
+ * color filter) to a remosaiced regular bayer pattern.</p>
+ * <p>This capability will only be present for devices with
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability. When
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * devices do not advertise this capability,
+ * {@link android.graphics.ImageFormat#RAW_SENSOR } images will already have a
+ * regular bayer pattern.</p>
+ * <p>If a <code>RAW_SENSOR</code> stream is requested along with another non-RAW stream in a
+ * {@link android.hardware.camera2.CaptureRequest } (if multiple streams are supported
+ * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }),
+ * the <code>RAW_SENSOR</code> stream will have a regular bayer pattern.</p>
+ * <p>This capability requires the camera device to support the following :
+ * * The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below
+ * refers to the one, described by
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.
+ * * One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.
+ * * {@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input
+ * format, that is, {@link android.graphics.ImageFormat#RAW_SENSOR } is included in the
+ * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.
+ * * {@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
+ * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.
+ * * Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}
+ * * Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate
+ * drop relative to the sensor's maximum capture rate (at that resolution).
+ * * No CaptureRequest controls will be applicable when a request has an input target
+ * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING = 17;
+
//
// Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
@@ -2955,6 +3012,27 @@
public static final int SENSOR_TEST_PATTERN_MODE_CUSTOM1 = 256;
//
+ // Enumeration values for CaptureRequest#SENSOR_PIXEL_MODE
+ //
+
+ /**
+ * <p>This is the default sensor pixel mode. This is the only sensor pixel mode
+ * supported unless a camera device advertises
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.</p>
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ public static final int SENSOR_PIXEL_MODE_DEFAULT = 0;
+
+ /**
+ * <p>This sensor pixel mode is offered by devices with capability
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.
+ * In this mode, sensors typically do not bin pixels, as a result can offer larger
+ * image sizes.</p>
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ */
+ public static final int SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION = 1;
+
+ //
// Enumeration values for CaptureRequest#SHADING_MODE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 9ac2ff5..906256d 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1391,6 +1391,13 @@
* scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
* activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
* mode.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
* distortion correction capability and mode</p>
@@ -1405,7 +1412,10 @@
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -1603,6 +1613,12 @@
* scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
* activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
* mode.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
* distortion correction capability and mode</p>
@@ -1617,7 +1633,10 @@
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -1808,6 +1827,12 @@
* the scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
* activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
* mode.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
* distortion correction capability and mode</p>
@@ -1822,7 +1847,10 @@
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -2896,6 +2924,12 @@
* coordinate system is post-zoom, meaning that the activeArraySize or
* preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See
* {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates relative to
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
@@ -2908,7 +2942,10 @@
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
* @see CameraCharacteristics#SCALER_CROPPING_TYPE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -3214,6 +3251,53 @@
new Key<Integer>("android.sensor.testPatternMode", int.class);
/**
+ * <p>Switches sensor pixel mode between maximum resolution mode and default mode.</p>
+ * <p>This key controls whether the camera sensor operates in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * mode or not. By default, all camera devices operate in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
+ * When operating in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
+ * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability would typically perform pixel binning in order to improve low light
+ * performance, noise reduction etc. However, in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * mode (supported only
+ * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+ * The stream configurations supported in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * mode are also different from those of
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
+ * They can be queried through
+ * {@link android.hardware.camera2.CameraCharacteristics#get } with
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+ * Unless reported by both
+ * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
+ * must not be mixed in the same CaptureRequest. In other words, these outputs are
+ * exclusive to each other.
+ * This key does not need to be set for reprocess requests.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
+ * <li>{@link #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION MAXIMUM_RESOLUTION}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+ * @see #SENSOR_PIXEL_MODE_DEFAULT
+ * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> SENSOR_PIXEL_MODE =
+ new Key<Integer>("android.sensor.pixelMode", int.class);
+
+ /**
* <p>Quality of lens shading correction applied
* to the image data.</p>
* <p>When set to OFF mode, no lens shading correction will be applied by the
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index e7457e7..6ff68c1 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -812,6 +812,13 @@
* scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
* activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
* mode.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
* distortion correction capability and mode</p>
@@ -826,7 +833,10 @@
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -1274,6 +1284,12 @@
* scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
* activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
* mode.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
* distortion correction capability and mode</p>
@@ -1288,7 +1304,10 @@
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -1890,6 +1909,12 @@
* the scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
* activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
* mode.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
* distortion correction capability and mode</p>
@@ -1904,7 +1929,10 @@
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -3565,6 +3593,12 @@
* coordinate system is post-zoom, meaning that the activeArraySize or
* preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See
* {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
+ * <p>For camera devices with the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+ * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
* <p><b>Units</b>: Pixel coordinates relative to
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
@@ -3577,7 +3611,10 @@
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
* @see CameraCharacteristics#SCALER_CROPPING_TYPE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
*/
@PublicKey
@NonNull
@@ -4107,6 +4144,69 @@
new Key<Integer>("android.sensor.dynamicWhiteLevel", int.class);
/**
+ * <p>Switches sensor pixel mode between maximum resolution mode and default mode.</p>
+ * <p>This key controls whether the camera sensor operates in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * mode or not. By default, all camera devices operate in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
+ * When operating in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
+ * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability would typically perform pixel binning in order to improve low light
+ * performance, noise reduction etc. However, in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * mode (supported only
+ * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+ * The stream configurations supported in
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+ * mode are also different from those of
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
+ * They can be queried through
+ * {@link android.hardware.camera2.CameraCharacteristics#get } with
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+ * Unless reported by both
+ * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
+ * must not be mixed in the same CaptureRequest. In other words, these outputs are
+ * exclusive to each other.
+ * This key does not need to be set for reprocess requests.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
+ * <li>{@link #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION MAXIMUM_RESOLUTION}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+ * @see #SENSOR_PIXEL_MODE_DEFAULT
+ * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> SENSOR_PIXEL_MODE =
+ new Key<Integer>("android.sensor.pixelMode", int.class);
+
+ /**
+ * <p>Whether <code>RAW</code> images requested have their bayer pattern as described by
+ * {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
+ * <p>This key will only be present in devices advertisting the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+ * capability which also advertise <code>REMOSAIC_REPROCESSING</code> capability. On all other devices
+ * RAW targets will have a regular bayer pattern.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Boolean> SENSOR_RAW_BINNING_FACTOR_USED =
+ new Key<Boolean>("android.sensor.rawBinningFactorUsed", boolean.class);
+
+ /**
* <p>Quality of lens shading correction applied
* to the image data.</p>
* <p>When set to OFF mode, no lens shading correction will be applied by the
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 0a42981..2920e67 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -20,6 +20,7 @@
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CameraOfflineSession;
import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
import android.hardware.camera2.CaptureRequest;
@@ -81,11 +82,17 @@
if (request == null) {
throw new IllegalArgumentException("Input capture request must not be null");
}
+ CameraCharacteristics.Key<StreamConfigurationMap> ck =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+ Integer sensorPixelMode = request.get(CaptureRequest.SENSOR_PIXEL_MODE);
+ if (sensorPixelMode != null && sensorPixelMode ==
+ CameraMetadata.SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION) {
+ ck = CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION;
+ }
Collection<Surface> outputSurfaces = request.getTargets();
Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
- StreamConfigurationMap config =
- mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ StreamConfigurationMap config = mCharacteristics.get(ck);
SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange, config);
// Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 4defd23..b578bf8 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.ImageFormat;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
@@ -62,6 +63,8 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -114,6 +117,7 @@
private final String mCameraId;
private final CameraCharacteristics mCharacteristics;
+ private final Map<String, CameraCharacteristics> mPhysicalIdsToChars;
private final int mTotalPartialCount;
private final Context mContext;
@@ -257,7 +261,9 @@
};
public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
- CameraCharacteristics characteristics, int appTargetSdkVersion,
+ CameraCharacteristics characteristics,
+ Map<String, CameraCharacteristics> physicalIdsToChars,
+ int appTargetSdkVersion,
Context ctx) {
if (cameraId == null || callback == null || executor == null || characteristics == null) {
throw new IllegalArgumentException("Null argument given");
@@ -266,6 +272,7 @@
mDeviceCallback = callback;
mDeviceExecutor = executor;
mCharacteristics = characteristics;
+ mPhysicalIdsToChars = physicalIdsToChars;
mAppTargetSdkVersion = appTargetSdkVersion;
mContext = ctx;
@@ -1357,11 +1364,71 @@
}
}
+ private boolean checkInputConfigurationWithStreamConfigurationsAs(
+ InputConfiguration inputConfig, StreamConfigurationMap configMap) {
+ int[] inputFormats = configMap.getInputFormats();
+ boolean validFormat = false;
+ int inputFormat = inputConfig.getFormat();
+ for (int format : inputFormats) {
+ if (format == inputFormat) {
+ validFormat = true;
+ }
+ }
+
+ if (validFormat == false) {
+ return false;
+ }
+
+ boolean validSize = false;
+ Size[] inputSizes = configMap.getInputSizes(inputFormat);
+ for (Size s : inputSizes) {
+ if (inputConfig.getWidth() == s.getWidth() &&
+ inputConfig.getHeight() == s.getHeight()) {
+ validSize = true;
+ }
+ }
+
+ if (validSize == false) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean checkInputConfigurationWithStreamConfigurations(
+ InputConfiguration inputConfig, boolean maxResolution) {
+ // Check if either this logical camera or any of its physical cameras support the
+ // input config. If they do, the input config is valid.
+ CameraCharacteristics.Key<StreamConfigurationMap> ck =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+
+ if (maxResolution) {
+ ck = CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION;
+ }
+
+ StreamConfigurationMap configMap = mCharacteristics.get(ck);
+
+ if (configMap != null &&
+ checkInputConfigurationWithStreamConfigurationsAs(inputConfig, configMap)) {
+ return true;
+ }
+
+ for (Map.Entry<String, CameraCharacteristics> entry : mPhysicalIdsToChars.entrySet()) {
+ configMap = entry.getValue().get(ck);
+
+ if (configMap != null &&
+ checkInputConfigurationWithStreamConfigurationsAs(inputConfig, configMap)) {
+ // Input config supported.
+ return true;
+ }
+ }
+ return false;
+ }
+
private void checkInputConfiguration(InputConfiguration inputConfig) {
if (inputConfig == null) {
return;
}
-
+ int inputFormat = inputConfig.getFormat();
if (inputConfig.isMultiResolution()) {
MultiResolutionStreamConfigurationMap configMap = mCharacteristics.get(
CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP);
@@ -1369,19 +1436,19 @@
int[] inputFormats = configMap.getInputFormats();
boolean validFormat = false;
for (int format : inputFormats) {
- if (format == inputConfig.getFormat()) {
+ if (format == inputFormat) {
validFormat = true;
}
}
if (validFormat == false) {
throw new IllegalArgumentException("multi-resolution input format " +
- inputConfig.getFormat() + " is not valid");
+ inputFormat + " is not valid");
}
boolean validSize = false;
Collection<MultiResolutionStreamInfo> inputStreamInfo =
- configMap.getInputInfo(inputConfig.getFormat());
+ configMap.getInputInfo(inputFormat);
for (MultiResolutionStreamInfo info : inputStreamInfo) {
if (inputConfig.getWidth() == info.getWidth() &&
inputConfig.getHeight() == info.getHeight()) {
@@ -1394,34 +1461,11 @@
inputConfig.getWidth() + "x" + inputConfig.getHeight() + " is not valid");
}
} else {
- StreamConfigurationMap configMap = mCharacteristics.get(
- CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-
- int[] inputFormats = configMap.getInputFormats();
- boolean validFormat = false;
- for (int format : inputFormats) {
- if (format == inputConfig.getFormat()) {
- validFormat = true;
- }
- }
-
- if (validFormat == false) {
- throw new IllegalArgumentException("input format " + inputConfig.getFormat() +
- " is not valid");
- }
-
- boolean validSize = false;
- Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat());
- for (Size s : inputSizes) {
- if (inputConfig.getWidth() == s.getWidth() &&
- inputConfig.getHeight() == s.getHeight()) {
- validSize = true;
- }
- }
-
- if (validSize == false) {
- throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" +
- inputConfig.getHeight() + " is not valid");
+ if (!checkInputConfigurationWithStreamConfigurations(inputConfig, /*maxRes*/false) &&
+ !checkInputConfigurationWithStreamConfigurations(inputConfig, /*maxRes*/true)) {
+ throw new IllegalArgumentException("Input config with format " +
+ inputFormat + " and size " + inputConfig.getWidth() + "x" +
+ inputConfig.getHeight() + " not supported by camera id " + mCameraId);
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 0cdf744..aa84b02 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -327,6 +327,10 @@
private static final String GPS_PROCESS = "GPS";
private static final int FACE_LANDMARK_SIZE = 6;
+ private static final int MANDATORY_STREAM_CONFIGURATIONS_DEFAULT = 0;
+ private static final int MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION = 1;
+ private static final int MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT = 2;
+
private static String translateLocationProviderToProcess(final String provider) {
if (provider == null) {
return null;
@@ -644,6 +648,15 @@
return (T) metadata.getStreamConfigurationMap();
}
});
+ sGetCommandMap.put(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION.getNativeKey(),
+ new GetCommand() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+ return (T) metadata.getStreamConfigurationMapMaximumResolution();
+ }
+ });
sGetCommandMap.put(
CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getNativeKey(),
new GetCommand() {
@@ -664,6 +677,16 @@
});
sGetCommandMap.put(
+ CameraCharacteristics.SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS.getNativeKey(),
+ new GetCommand() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+ return (T) metadata.getMandatoryMaximumResolutionStreamCombinations();
+ }
+ });
+
+ sGetCommandMap.put(
CameraCharacteristics.CONTROL_MAX_REGIONS_AE.getNativeKey(), new GetCommand() {
@Override
@SuppressWarnings("unchecked")
@@ -1285,12 +1308,12 @@
return recommendedConfigurations;
}
- private boolean isBurstSupported() {
+ private boolean isCapabilitySupported(int capabilityRequested) {
boolean ret = false;
int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
for (int capability : capabilities) {
- if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE) {
+ if (capabilityRequested == capability) {
ret = true;
break;
}
@@ -1299,8 +1322,18 @@
return ret;
}
+ private boolean isUltraHighResolutionSensor() {
+ return isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR);
+
+ }
+ private boolean isBurstSupported() {
+ return isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE);
+ }
+
private MandatoryStreamCombination[] getMandatoryStreamCombinationsHelper(
- boolean getConcurrent) {
+ int mandatoryStreamsType) {
int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
ArrayList<Integer> caps = new ArrayList<Integer>();
caps.ensureCapacity(capabilities.length);
@@ -1309,20 +1342,25 @@
}
int hwLevel = getBase(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
MandatoryStreamCombination.Builder build = new MandatoryStreamCombination.Builder(
- mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap());
+ mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap(),
+ getStreamConfigurationMapMaximumResolution());
List<MandatoryStreamCombination> combs = null;
- if (getConcurrent) {
- combs = build.getAvailableMandatoryConcurrentStreamCombinations();
- } else {
- combs = build.getAvailableMandatoryStreamCombinations();
+ switch (mandatoryStreamsType) {
+ case MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT:
+ combs = build.getAvailableMandatoryConcurrentStreamCombinations();
+ break;
+ case MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION:
+ combs = build.getAvailableMandatoryMaximumResolutionStreamCombinations();
+ break;
+ default:
+ combs = build.getAvailableMandatoryStreamCombinations();
}
if ((combs != null) && (!combs.isEmpty())) {
MandatoryStreamCombination[] combArray = new MandatoryStreamCombination[combs.size()];
combArray = combs.toArray(combArray);
return combArray;
}
-
return null;
}
@@ -1330,11 +1368,18 @@
if (!mHasMandatoryConcurrentStreams) {
return null;
}
- return getMandatoryStreamCombinationsHelper(true);
+ return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT);
+ }
+
+ private MandatoryStreamCombination[] getMandatoryMaximumResolutionStreamCombinations() {
+ if (!isUltraHighResolutionSensor()) {
+ return null;
+ }
+ return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION);
}
private MandatoryStreamCombination[] getMandatoryStreamCombinations() {
- return getMandatoryStreamCombinationsHelper(false);
+ return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_DEFAULT);
}
private StreamConfigurationMap getStreamConfigurationMap() {
@@ -1377,6 +1422,50 @@
listHighResolution);
}
+ private StreamConfigurationMap getStreamConfigurationMapMaximumResolution() {
+ if (!isUltraHighResolutionSensor()) {
+ return null;
+ }
+ StreamConfiguration[] configurations = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] minFrameDurations = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] stallDurations = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfiguration[] depthConfigurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] depthMinFrameDurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] depthStallDurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfiguration[] dynamicDepthConfigurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] dynamicDepthMinFrameDurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] dynamicDepthStallDurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfiguration[] heicConfigurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] heicMinFrameDurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] heicStallDurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
+ CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ ReprocessFormatsMap inputOutputFormatsMap = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP_MAXIMUM_RESOLUTION);
+ // TODO: Is this correct, burst capability shouldn't necessarily correspond to max res mode
+ boolean listHighResolution = isBurstSupported();
+ return new StreamConfigurationMap(
+ configurations, minFrameDurations, stallDurations,
+ depthConfigurations, depthMinFrameDurations, depthStallDurations,
+ dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
+ dynamicDepthStallDurations, heicConfigurations,
+ heicMinFrameDurations, heicStallDurations,
+ highSpeedVideoConfigurations, inputOutputFormatsMap,
+ listHighResolution, false);
+ }
+
private <T> Integer getMaxRegions(Key<T> key) {
final int AE = 0;
final int AWB = 1;
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 8a0172e..34116aa 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -267,8 +267,8 @@
mStreamsInformation.hashCode());
}
- private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM, s720p, s1440p }
- private static enum ReprocessType { NONE, PRIVATE, YUV }
+ private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM, s720p, s1440p, FULL_RES }
+ private static enum ReprocessType { NONE, PRIVATE, YUV, REMOSAIC }
private static final class StreamTemplate {
public int mFormat;
public SizeThreshold mSizeThreshold;
@@ -691,6 +691,18 @@
"Depth capture for mesh based object rendering"),
};
+ private static StreamCombinationTemplate sUltraHighResolutionStreamCombinations[] = {
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.FULL_RES) },
+ "Full res YUV image capture"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.FULL_RES) },
+ "Full res RAW capture"),
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.JPEG, SizeThreshold.FULL_RES) },
+ "Full res JPEG still image capture"),
+ };
+
/**
* Helper builder class to generate a list of available mandatory stream combinations.
* @hide
@@ -700,6 +712,7 @@
private List<Integer> mCapabilities;
private int mHwLevel, mCameraId;
private StreamConfigurationMap mStreamConfigMap;
+ private StreamConfigurationMap mStreamConfigMapMaximumResolution;
private boolean mIsHiddenPhysicalCamera;
private final Size kPreviewSizeBound = new Size(1920, 1088);
@@ -712,13 +725,17 @@
* @param displaySize The device display size.
* @param capabilities The camera device capabilities.
* @param sm The camera device stream configuration map.
+ * @param smMaxResolution The camera device stream configuration map when it runs in max
+ * resolution mode.
*/
public Builder(int cameraId, int hwLevel, @NonNull Size displaySize,
- @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm) {
+ @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm,
+ StreamConfigurationMap smMaxResolution) {
mCameraId = cameraId;
mDisplaySize = displaySize;
mCapabilities = capabilities;
mStreamConfigMap = sm;
+ mStreamConfigMapMaximumResolution = smMaxResolution;
mHwLevel = hwLevel;
mIsHiddenPhysicalCamera =
CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId));
@@ -797,6 +814,97 @@
}
/**
+ * Retrieve a list of all available mandatory stream combinations supported when
+ * {@link CaptureRequest#ANDROID_SENSOR_PIXEL_MODE} is set to
+ * {@link CameraMetadata#ANDROID_SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION}.
+ *
+ * @return a non-modifiable list of supported mandatory stream combinations or
+ * null in case device is not backward compatible or the method encounters
+ * an error.
+ */
+ public @NonNull List<MandatoryStreamCombination>
+ getAvailableMandatoryMaximumResolutionStreamCombinations() {
+
+ ArrayList<StreamCombinationTemplate> chosenStreamCombinations =
+ new ArrayList<StreamCombinationTemplate>();
+
+ chosenStreamCombinations.addAll(Arrays.asList(sUltraHighResolutionStreamCombinations));
+
+ ArrayList<MandatoryStreamCombination> availableStreamCombinations =
+ new ArrayList<MandatoryStreamCombination>();
+ boolean addRemosaicReprocessing = isRemosaicReprocessingSupported();
+ int remosaicSize = 0;
+ if (addRemosaicReprocessing) {
+ remosaicSize = 1;
+ }
+ availableStreamCombinations.ensureCapacity(
+ chosenStreamCombinations.size() + remosaicSize);
+ fillMandatoryOutputStreamCombinations(availableStreamCombinations,
+ chosenStreamCombinations, mStreamConfigMapMaximumResolution);
+ if (isRemosaicReprocessingSupported()) {
+ // Add reprocess mandatory streams
+ ArrayList<MandatoryStreamInformation> streamsInfo =
+ new ArrayList<MandatoryStreamInformation>();
+
+ ArrayList<Size> inputSize = new ArrayList<Size>();
+ Size maxRawInputSize = getMaxSize(mStreamConfigMapMaximumResolution.getInputSizes(
+ ImageFormat.RAW_SENSOR));
+ inputSize.add(maxRawInputSize);
+
+ streamsInfo.add(new MandatoryStreamInformation(inputSize,
+ ImageFormat.RAW_SENSOR, /*isInput*/true));
+ streamsInfo.add(new MandatoryStreamInformation(inputSize,
+ ImageFormat.RAW_SENSOR));
+ MandatoryStreamCombination streamCombination;
+ streamCombination = new MandatoryStreamCombination(streamsInfo,
+ "Remosaic reprocessing", true);
+ availableStreamCombinations.add(streamCombination);
+ }
+ return Collections.unmodifiableList(availableStreamCombinations);
+ }
+
+ private void fillMandatoryOutputStreamCombinations(
+ ArrayList<MandatoryStreamCombination> availableStreamCombinations,
+ ArrayList<StreamCombinationTemplate> chosenStreamCombinations,
+ StreamConfigurationMap streamConfigMap) {
+
+ for (StreamCombinationTemplate combTemplate : chosenStreamCombinations) {
+ ArrayList<MandatoryStreamInformation> streamsInfo =
+ new ArrayList<MandatoryStreamInformation>();
+ streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
+
+ for (StreamTemplate template : combTemplate.mStreamTemplates) {
+ MandatoryStreamInformation streamInfo;
+ List<Size> sizes = new ArrayList<Size>();
+ Size sizeChosen =
+ getMaxSize(streamConfigMap.getOutputSizes(
+ template.mFormat));
+ sizes.add(sizeChosen);
+ try {
+ streamInfo = new MandatoryStreamInformation(sizes, template.mFormat);
+ } catch (IllegalArgumentException e) {
+ String cause = "No available sizes found for format: " + template.mFormat
+ + " size threshold: " + template.mSizeThreshold + " combination: "
+ + combTemplate.mDescription;
+ throw new RuntimeException(cause, e);
+ }
+ streamsInfo.add(streamInfo);
+ }
+
+ MandatoryStreamCombination streamCombination;
+ try {
+ streamCombination = new MandatoryStreamCombination(streamsInfo,
+ combTemplate.mDescription, /*isReprocess*/false);
+ } catch (IllegalArgumentException e) {
+ String cause = "No stream information for mandatory combination: "
+ + combTemplate.mDescription;
+ throw new RuntimeException(cause, e);
+ }
+ availableStreamCombinations.add(streamCombination);
+ }
+ }
+
+ /**
* Retrieve a list of all available mandatory stream combinations.
*
* @return a non-modifiable list of supported mandatory stream combinations or
@@ -948,7 +1056,6 @@
inputSize.add(maxYUVInputSize);
format = ImageFormat.YUV_420_888;
}
-
streamsInfo.add(new MandatoryStreamInformation(inputSize, format,
/*isInput*/true));
streamsInfo.add(new MandatoryStreamInformation(inputSize, format));
@@ -974,7 +1081,6 @@
combTemplate.mDescription);
return null;
}
-
streamsInfo.add(streamInfo);
}
@@ -1220,6 +1326,14 @@
}
/**
+ * Check whether the current device supports YUV reprocessing.
+ */
+ private boolean isRemosaicReprocessingSupported() {
+ return isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING);
+ }
+
+ /**
* Return the maximum supported video size using the camcorder profile information.
*
* @return Maximum supported video size.
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index e31bd60..84736dc 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -19,6 +19,7 @@
import static com.android.internal.util.Preconditions.*;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -26,6 +27,7 @@
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.MultiResolutionImageReader;
import android.hardware.camera2.params.MultiResolutionStreamInfo;
import android.hardware.camera2.utils.HashCodeHelpers;
@@ -33,10 +35,13 @@
import android.media.ImageReader;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -145,6 +150,13 @@
*/
public static final int SURFACE_GROUP_ID_NONE = -1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SENSOR_PIXEL_MODE_"}, value =
+ {CameraMetadata.SENSOR_PIXEL_MODE_DEFAULT,
+ CameraMetadata.SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION})
+ public @interface SensorPixelMode {};
+
/**
* Create a new {@link OutputConfiguration} instance with a {@link Surface}.
*
@@ -306,6 +318,7 @@
mIsShared = false;
mPhysicalCameraId = null;
mIsMultiResolution = false;
+ mSensorPixelModesUsed = new ArrayList<Integer>();
}
/**
@@ -399,6 +412,7 @@
mIsShared = false;
mPhysicalCameraId = null;
mIsMultiResolution = false;
+ mSensorPixelModesUsed = new ArrayList<Integer>();
}
/**
@@ -485,6 +499,81 @@
}
/**
+ * Add a sensor pixel mode that this OutputConfiguration will be used in.
+ *
+ * <p> In the case that this output stream configuration (format, width, height) is
+ * available through {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP}
+ * configurations and
+ * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION},
+ * configurations, the camera sub-system will assume that this {@link OutputConfiguration} will
+ * be used only with {@link android.hardware.camera2.CaptureRequest}s which has
+ * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT}.
+ * In such cases, if clients intend to use the
+ * {@link OutputConfiguration}(s) in a {@link android.hardware.camera2.CaptureRequest} with
+ * other sensor pixel modes, they must specify which
+ * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE}(s) they will use this
+ * {@link OutputConfiguration} with, by calling this method.
+ *
+ * In case this output stream configuration (format, width, height) is only in
+ * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION},
+ * configurations, this output target must only be used with
+ * {@link android.hardware.camera2.CaptureRequest}s which has
+ * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION} and that
+ * is what the camera sub-system will assume. If clients add
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT} in this
+ * case, session configuration will fail, if this {@link OutputConfiguration} is included.
+ *
+ * In case this output stream configuration (format, width, height) is only in
+ * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP},
+ * configurations, this output target must only be used with
+ * {@link android.hardware.camera2.CaptureRequest}s which has
+ * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE} set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT} and that is what
+ * the camera sub-system will assume. If clients add
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION} in this
+ * case, session configuration will fail, if this {@link OutputConfiguration} is included.
+ *
+ * @param sensorPixelModeUsed The sensor pixel mode this OutputConfiguration will be used with
+ * </p>
+ *
+ */
+ public void addSensorPixelModeUsed(@SensorPixelMode int sensorPixelModeUsed) {
+ // Verify that the values are in range.
+ if (sensorPixelModeUsed != CameraMetadata.SENSOR_PIXEL_MODE_DEFAULT &&
+ sensorPixelModeUsed != CameraMetadata.SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION) {
+ throw new IllegalArgumentException("Not a valid sensor pixel mode " +
+ sensorPixelModeUsed);
+ }
+
+ if (mSensorPixelModesUsed.contains(sensorPixelModeUsed)) {
+ // Already added, ignore;
+ return;
+ }
+ mSensorPixelModesUsed.add(sensorPixelModeUsed);
+ }
+
+ /**
+ * Remove a sensor pixel mode, previously added through addSensorPixelModeUsed, from this
+ * OutputConfiguration.
+ *
+ * <p> Sensor pixel modes added via calls to {@link #addSensorPixelModeUsed} can also be removed
+ * from the OutputConfiguration.</p>
+ *
+ * @param sensorPixelModeUsed The sensor pixel mode to be removed.
+ *
+ * @throws IllegalArgumentException If the sensor pixel mode wasn't previously added
+ * through {@link #addSensorPixelModeUsed}.
+ */
+ public void removeSensorPixelModeUsed(@SensorPixelMode int sensorPixelModeUsed) {
+ if (!mSensorPixelModesUsed.remove(Integer.valueOf(sensorPixelModeUsed))) {
+ throw new IllegalArgumentException("sensorPixelMode " + sensorPixelModeUsed +
+ "is not part of this output configuration");
+ }
+ }
+
+ /**
* Check if this configuration is for a physical camera.
*
* <p>This returns true if the output configuration was for a physical camera making up a
@@ -625,6 +714,7 @@
this.mIsShared = other.mIsShared;
this.mPhysicalCameraId = other.mPhysicalCameraId;
this.mIsMultiResolution = other.mIsMultiResolution;
+ this.mSensorPixelModesUsed = other.mSensorPixelModesUsed;
}
/**
@@ -642,7 +732,8 @@
source.readTypedList(surfaces, Surface.CREATOR);
String physicalCameraId = source.readString();
boolean isMultiResolutionOutput = source.readInt() == 1;
-
+ ArrayList<Integer> sensorPixelModesUsed = new ArrayList<Integer>();
+ source.readList(sensorPixelModesUsed, Integer.class.getClassLoader());
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
mSurfaceGroupId = surfaceSetId;
@@ -666,6 +757,7 @@
}
mPhysicalCameraId = physicalCameraId;
mIsMultiResolution = isMultiResolutionOutput;
+ mSensorPixelModesUsed = sensorPixelModesUsed;
}
/**
@@ -766,6 +858,7 @@
dest.writeTypedList(mSurfaces);
dest.writeString(mPhysicalCameraId);
dest.writeInt(mIsMultiResolution ? 1 : 0);
+ dest.writeList(mSensorPixelModesUsed);
}
/**
@@ -798,7 +891,14 @@
!Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
mIsMultiResolution != other.mIsMultiResolution)
return false;
-
+ if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
+ return false;
+ }
+ for (int j = 0; j < mSensorPixelModesUsed.size(); j++) {
+ if (mSensorPixelModesUsed.get(j) != other.mSensorPixelModesUsed.get(j)) {
+ return false;
+ }
+ }
int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size());
for (int i = 0; i < minLen; i++) {
if (mSurfaces.get(i) != other.mSurfaces.get(i))
@@ -823,7 +923,7 @@
mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0,
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
- mIsMultiResolution ? 1 : 0);
+ mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode());
}
return HashCodeHelpers.hashCode(
@@ -831,7 +931,7 @@
mConfiguredSize.hashCode(), mConfiguredFormat,
mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0,
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
- mIsMultiResolution ? 1 : 0);
+ mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode());
}
private static final String TAG = "OutputConfiguration";
@@ -861,4 +961,6 @@
// Flag indicating if this config is for a multi-resolution output with a
// MultiResolutionImageReader
private boolean mIsMultiResolution;
+ // The sensor pixel modes that this OutputConfiguration will use
+ private ArrayList<Integer> mSensorPixelModesUsed;
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 6dd6744..2430c09 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -19,6 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.Manifest;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
@@ -921,6 +922,43 @@
mGlobal.setTemporaryBrightness(displayId, brightness);
}
+
+ /**
+ * Sets the brightness of the specified display.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId the logical display id
+ * @param brightness The brightness value from 0.0f to 1.0f.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ public void setBrightness(int displayId, @FloatRange(from = 0f, to = 1f) float brightness) {
+ mGlobal.setBrightness(displayId, brightness);
+ }
+
+
+ /**
+ * Gets the brightness of the specified display.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The display of which brightness value to get from.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getBrightness(int displayId) {
+ return mGlobal.getBrightness(displayId);
+ }
+
+
/**
* Temporarily sets the auto brightness adjustment factor.
* <p>
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index fd0431c5..06efc4f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -690,6 +690,37 @@
}
}
+
+ /**
+ * Sets the brightness of the display.
+ *
+ * @param brightness The brightness value from 0.0f to 1.0f.
+ *
+ * @hide
+ */
+ public void setBrightness(int displayId, float brightness) {
+ try {
+ mDm.setBrightness(displayId, brightness);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the brightness of the display.
+ *
+ * @param displayId The display from which to get the brightness
+ *
+ * @hide
+ */
+ public float getBrightness(int displayId) {
+ try {
+ return mDm.getBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
/**
* Temporarily sets the auto brightness adjustment factor.
* <p>
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index dee9144..3538ff1 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -119,6 +119,12 @@
// Temporarily sets the display brightness.
void setTemporaryBrightness(int displayId, float brightness);
+ // Saves the display brightness.
+ void setBrightness(int displayId, float brightness);
+
+ // Retrieves the display brightness.
+ float getBrightness(int displayId);
+
// Temporarily sets the auto brightness adjustment factor.
void setTemporaryAutoBrightnessAdjustment(float adjustment);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 01fd396..c83ccfa 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -223,6 +223,14 @@
public static final long BLOCK_FLAG_SLIPPERY = android.os.IInputConstants.BLOCK_FLAG_SLIPPERY;
/**
+ * Check whether apps are using MotionEvent.getRawX/Y. This is implementation-specific, and
+ * thus undefined for most 3p app usages.
+ * @hide
+ */
+ @ChangeId
+ public static final long APP_USES_RAW_INPUT_COORDS = 179274888L;
+
+ /**
* Input Event Injection Synchronization Mode: None.
* Never blocks. Injection is asynchronous and is assumed always to be successful.
* @hide
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
index 65b336a..23fc069 100644
--- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
@@ -147,10 +147,7 @@
/**
* Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached.
- *
- * @hide
*/
- // TODO: Expose as system API.
public void notifyWarningReached() {
try {
// Reuse the code path to notify warning reached with limit reached
@@ -198,7 +195,6 @@
* @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
* from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
*/
- // TODO: deprecate this once onSetWarningAndLimit is ready.
public abstract void onSetLimit(@NonNull String iface, long quotaBytes);
/**
@@ -217,10 +213,7 @@
* there is no warning.
* @param limitBytes the limit defined as the number of bytes, starting from zero and counting
* from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
- *
- * @hide
*/
- // TODO: Expose as system API.
public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) {
// Backward compatibility for those who didn't override this function.
onSetLimit(iface, limitBytes);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 4674aa2..c47fc57 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1004,6 +1004,24 @@
public abstract long getCpuMeasuredBatteryConsumptionUC();
/**
+ * Returns the battery consumption (in microcoulombs) of the uid's GNSS usage, derived from
+ * on device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getGnssMeasuredBatteryConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's radio usage, derived from
+ * on device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
+
+ /**
* Returns the battery consumption (in microcoulombs) of the screen while on and uid active,
* derived from on device power measurement data.
* Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
@@ -2548,6 +2566,24 @@
public abstract long getCpuMeasuredBatteryConsumptionUC();
/**
+ * Returns the battery consumption (in microcoulombs) of the GNSS, derived from on device power
+ * measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getGnssMeasuredBatteryConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the radio, derived from on device power
+ * measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
+
+ /**
* Returns the battery consumption (in microcoulombs) of the screen while on, derived from on
* device power measurement data.
* Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 9518bf1..85861bc 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -60,14 +60,18 @@
*/
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 2;
+ private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
+
private final int mFlags;
@NonNull
private final int[] mUserIds;
+ private final long mMaxStatsAgeMs;
private BatteryUsageStatsQuery(@NonNull Builder builder) {
mFlags = builder.mFlags;
mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
: new int[]{UserHandle.USER_ALL};
+ mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
}
@BatteryUsageStatsFlags
@@ -94,10 +98,19 @@
return (mFlags & FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0;
}
+ /**
+ * Returns the client's tolerance for stale battery stats. The data is allowed to be up to
+ * this many milliseconds out-of-date.
+ */
+ public long getMaxStatsAge() {
+ return mMaxStatsAgeMs;
+ }
+
private BatteryUsageStatsQuery(Parcel in) {
mFlags = in.readInt();
mUserIds = new int[in.readInt()];
in.readIntArray(mUserIds);
+ mMaxStatsAgeMs = in.readLong();
}
@Override
@@ -105,6 +118,7 @@
dest.writeInt(mFlags);
dest.writeInt(mUserIds.length);
dest.writeIntArray(mUserIds);
+ dest.writeLong(mMaxStatsAgeMs);
}
@Override
@@ -132,6 +146,7 @@
public static final class Builder {
private int mFlags;
private IntArray mUserIds;
+ private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
/**
* Builds a read-only BatteryUsageStatsQuery object.
@@ -170,5 +185,14 @@
mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL;
return this;
}
+
+ /**
+ * Set the client's tolerance for stale battery stats. The data may be up to
+ * this many milliseconds out-of-date.
+ */
+ public Builder setMaxStatsAgeMs(long maxStatsAgeMs) {
+ mMaxStatsAgeMs = maxStatsAgeMs;
+ return this;
+ }
}
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 7669586cf..baa25f0 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -80,6 +80,9 @@
private static final String LOG_TAG = PermissionManager.class.getName();
/** @hide */
+ public static final String LOG_TAG_TRACE_GRANTS = "PermissionGrantTrace";
+
+ /** @hide */
public static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
"permissions revoked";
/** @hide */
@@ -103,6 +106,8 @@
* Note: Changing this won't do anything on its own - you should also change the filtering in
* {@link #shouldTraceGrant}.
*
+ * See log output for tag {@link #LOG_TAG_TRACE_GRANTS}
+ *
* @hide
*/
public static final boolean DEBUG_TRACE_GRANTS = false;
@@ -319,8 +324,10 @@
}
/** @hide */
- public static boolean shouldTraceGrant(String packageName, String permissionName, int userId) {
+ public static boolean shouldTraceGrant(
+ @NonNull String packageName, @NonNull String permissionName, int userId) {
// To be modified when debugging
+ // template: if ("".equals(packageName) && "".equals(permissionName)) return true;
return false;
}
@@ -348,7 +355,8 @@
@NonNull String permissionName, @NonNull UserHandle user) {
if (DEBUG_TRACE_GRANTS
&& shouldTraceGrant(packageName, permissionName, user.getIdentifier())) {
- Log.i(LOG_TAG, "App " + mContext.getPackageName() + " is granting " + packageName + " "
+ Log.i(LOG_TAG_TRACE_GRANTS, "App " + mContext.getPackageName() + " is granting "
+ + packageName + " "
+ permissionName + " for user " + user.getIdentifier(), new RuntimeException());
}
try {
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 31cf63c..03b5a2e 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -257,6 +257,14 @@
public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
/**
+ * Namespace for all media related features.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_MEDIA = "media";
+
+ /**
* Namespace for all media native related features.
*
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 61fbef5..5ab462b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -470,8 +470,8 @@
* to be shown, with the "package" scheme. That is "package:com.my.app".
* <p>
* Output: Nothing.
- * @hide
*/
+ @SuppressLint("ActionValue")
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS =
"com.android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
@@ -6291,6 +6291,20 @@
"selected_input_method_subtype";
/**
+ * The {@link android.view.inputmethod.InputMethodInfo.InputMethodInfo#getId() ID} of the
+ * default voice input method.
+ * <p>
+ * This stores the last known default voice IME. If the related system config value changes,
+ * this is reset by InputMethodManagerService.
+ * <p>
+ * This IME is not necessarily in the enabled IME list. That state is still stored in
+ * {@link #ENABLED_INPUT_METHODS}.
+ *
+ * @hide
+ */
+ public static final String DEFAULT_VOICE_INPUT_METHOD = "default_voice_input_method";
+
+ /**
* Setting to record the history of input method subtype, holding the pair of ID of IME
* and its last used subtype.
* @hide
@@ -9162,6 +9176,22 @@
public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete";
/**
+ * Whether the assistant can be triggered by a touch gesture.
+ *
+ * @hide
+ */
+ public static final String ASSIST_TOUCH_GESTURE_ENABLED =
+ "assist_touch_gesture_enabled";
+
+ /**
+ * Whether the assistant can be triggered by long-pressing the home button
+ *
+ * @hide
+ */
+ public static final String ASSIST_LONG_PRESS_HOME_ENABLED =
+ "assist_long_press_home_enabled";
+
+ /**
* Control whether Trust Agents are in active unlock or extend unlock mode.
* @hide
*/
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 04a4ca4..13274c6 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -38,6 +38,8 @@
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
+import com.android.internal.os.IResultReceiver;
+
/**
* An {@code AutofillService} is a service used to automatically fill the contents of the screen
* on behalf of a given user - for more information about autofill, read
@@ -575,6 +577,20 @@
*/
public static final String SERVICE_META_DATA = "android.autofill";
+ /**
+ * Name of the {@link IResultReceiver} extra used to return the primary result of a request.
+ *
+ * @hide
+ */
+ public static final String EXTRA_RESULT = "result";
+
+ /**
+ * Name of the {@link IResultReceiver} extra used to return the error reason of a request.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ERROR = "error";
+
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void onConnectedStateChanged(boolean connected) {
@@ -603,6 +619,14 @@
AutofillService::onSaveRequest,
AutofillService.this, request, new SaveCallback(callback)));
}
+
+ @Override
+ public void onSavedPasswordCountRequest(IResultReceiver receiver) {
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onSavedDatasetsInfoRequest,
+ AutofillService.this,
+ new SavedDatasetsInfoCallbackImpl(receiver, SavedDatasetsInfo.TYPE_PASSWORDS)));
+ }
};
private Handler mHandler;
@@ -673,6 +697,19 @@
@NonNull SaveCallback callback);
/**
+ * Called from system settings to display information about the datasets the user saved to this
+ * service.
+ *
+ * <p>There is no timeout for the request, but it's recommended to return the result within a
+ * few seconds, or the user may navigate away from the activity that would display the result.
+ *
+ * @param callback callback for responding to the request
+ */
+ public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) {
+ callback.onError(SavedDatasetsInfoCallback.ERROR_UNSUPPORTED);
+ }
+
+ /**
* Called when the Android system disconnects from the service.
*
* <p> At this point this service may no longer be an active {@link AutofillService}.
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 23a1a3f..d88e094 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -31,4 +31,5 @@
void onConnectedStateChanged(boolean connected);
void onFillRequest(in FillRequest request, in IFillCallback callback);
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
+ void onSavedPasswordCountRequest(in IResultReceiver receiver);
}
diff --git a/core/java/android/service/autofill/SavedDatasetsInfo.java b/core/java/android/service/autofill/SavedDatasetsInfo.java
new file mode 100644
index 0000000..6a4d2b8
--- /dev/null
+++ b/core/java/android/service/autofill/SavedDatasetsInfo.java
@@ -0,0 +1,186 @@
+/*
+ * 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.autofill;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A result returned from
+ * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}.
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstDefs = true,
+ genEqualsHashCode = true)
+public final class SavedDatasetsInfo {
+
+ /**
+ * Any other type of datasets.
+ */
+ public static final String TYPE_OTHER = "other";
+
+ /**
+ * Datasets such as login credentials.
+ */
+ public static final String TYPE_PASSWORDS = "passwords";
+
+ /**
+ * The type of the datasets that this info is about.
+ */
+ @NonNull
+ @Type
+ private final String mType;
+
+ /**
+ * The number of datasets of {@link #getType() this type} that the user has saved to the
+ * service.
+ */
+ @IntRange(from = 0)
+ private final int mCount;
+
+
+ // 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/autofill/SavedDatasetsInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @StringDef(prefix = "TYPE_", value = {
+ TYPE_OTHER,
+ TYPE_PASSWORDS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Type {}
+
+ /**
+ * Creates a new SavedDatasetsInfo.
+ *
+ * @param type
+ * The type of the datasets.
+ * @param count
+ * The number of datasets of this type that the user has saved to the service.
+ */
+ @DataClass.Generated.Member
+ public SavedDatasetsInfo(
+ @NonNull @Type String type,
+ @IntRange(from = 0) int count) {
+ this.mType = type;
+
+ if (!(java.util.Objects.equals(mType, TYPE_OTHER))
+ && !(java.util.Objects.equals(mType, TYPE_PASSWORDS))) {
+ throw new java.lang.IllegalArgumentException(
+ "type was " + mType + " but must be one of: "
+ + "TYPE_OTHER(" + TYPE_OTHER + "), "
+ + "TYPE_PASSWORDS(" + TYPE_PASSWORDS + ")");
+ }
+
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mType);
+ this.mCount = count;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mCount,
+ "from", 0);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The type of the datasets.
+ */
+ @DataClass.Generated.Member
+ public @NonNull @Type String getType() {
+ return mType;
+ }
+
+ /**
+ * The number of datasets of this type that the user has saved to the service.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getCount() {
+ return mCount;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "SavedDatasetsInfo { " +
+ "type = " + mType + ", " +
+ "count = " + mCount +
+ " }";
+ }
+
+ @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(SavedDatasetsInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ SavedDatasetsInfo that = (SavedDatasetsInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mType, that.mType)
+ && mCount == that.mCount;
+ }
+
+ @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(mType);
+ _hash = 31 * _hash + mCount;
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1615325704446L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java",
+ inputSignatures = "public static final java.lang.String TYPE_OTHER\npublic static final java.lang.String TYPE_PASSWORDS\nprivate final @android.annotation.NonNull @android.service.autofill.SavedDatasetsInfo.Type java.lang.String mType\nprivate final @android.annotation.IntRange int mCount\nclass SavedDatasetsInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallback.java b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java
new file mode 100644
index 0000000..a47105a
--- /dev/null
+++ b/core/java/android/service/autofill/SavedDatasetsInfoCallback.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.service.autofill;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Handles the response to
+ * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}.
+ * <p>
+ * Use {@link #onSuccess(Set)} to return the computed info about the datasets the user saved to this
+ * service. If there was an error querying the info, or if the service is unable to do so at this
+ * time (for example, if the user isn't logged in), call {@link #onError(int)}.
+ * <p>
+ * This callback can be used only once.
+ */
+public interface SavedDatasetsInfoCallback {
+
+ /** @hide */
+ @IntDef(prefix = {"ERROR_"}, value = {
+ ERROR_OTHER,
+ ERROR_UNSUPPORTED,
+ ERROR_NEEDS_USER_ACTION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Error {
+ }
+
+ /**
+ * The result could not be computed for any other reason.
+ */
+ int ERROR_OTHER = 0;
+ /**
+ * The service does not support this request.
+ */
+ int ERROR_UNSUPPORTED = 1;
+ /**
+ * The result cannot be computed until the user takes some action, such as setting up their
+ * account.
+ */
+ int ERROR_NEEDS_USER_ACTION = 2;
+
+ /**
+ * Successfully respond to the request with the info on each type of saved datasets.
+ */
+ void onSuccess(@NonNull Set<SavedDatasetsInfo> results);
+
+ /**
+ * Respond to the request with an error. System settings may display a suitable notice to the
+ * user.
+ */
+ void onError(@Error int error);
+}
diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java
new file mode 100644
index 0000000..b8a8cde
--- /dev/null
+++ b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java
@@ -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 android.service.autofill;
+
+import static android.service.autofill.AutofillService.EXTRA_ERROR;
+import static android.service.autofill.AutofillService.EXTRA_RESULT;
+
+import static com.android.internal.util.Preconditions.checkArgumentInRange;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.util.Set;
+
+final class SavedDatasetsInfoCallbackImpl implements SavedDatasetsInfoCallback {
+ private static final String TAG = "AutofillService";
+
+ @NonNull
+ private final IResultReceiver mReceiver;
+ @NonNull
+ private final String mType;
+
+ /**
+ * Creates a {@link SavedDatasetsInfoCallback} that returns the {@link
+ * SavedDatasetsInfo#getCount() number} of saved datasets of {@code type} to the {@code
+ * receiver}.
+ */
+ SavedDatasetsInfoCallbackImpl(@NonNull IResultReceiver receiver, @NonNull String type) {
+ mReceiver = requireNonNull(receiver);
+ mType = requireNonNull(type);
+ }
+
+ @Override
+ public void onSuccess(@NonNull Set<SavedDatasetsInfo> results) {
+ requireNonNull(results);
+ if (results.isEmpty()) {
+ send(1, null);
+ return;
+ }
+ int count = -1;
+ for (SavedDatasetsInfo info : results) {
+ if (mType.equals(info.getType())) {
+ count = info.getCount();
+ }
+ }
+ if (count < 0) {
+ send(1, null);
+ return;
+ }
+ Bundle bundle = new Bundle(/* capacity= */ 1);
+ bundle.putInt(EXTRA_RESULT, count);
+ send(0, bundle);
+ }
+
+ @Override
+ public void onError(@Error int error) {
+ checkArgumentInRange(error, ERROR_OTHER, ERROR_NEEDS_USER_ACTION, "error");
+ Bundle bundle = new Bundle(/* capacity= */ 1);
+ bundle.putInt(EXTRA_ERROR, error);
+ send(1, bundle);
+ }
+
+ private void send(int resultCode, Bundle bundle) {
+ try {
+ mReceiver.send(resultCode, bundle);
+ } catch (DeadObjectException e) {
+ Log.w(TAG, "Failed to send onSavedPasswordCountRequest result: " + e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index aeeaa97..d9a310f 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -618,7 +618,7 @@
mFirstOnSuccessTime = SystemClock.elapsedRealtime();
duration = mFirstOnSuccessTime - mFirstRequestTime;
if (sDebug) {
- Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
+ Log.d(TAG, "Inline response in " + formatDuration(duration));
}
}
} break;
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 1ea40be..94ca68f 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -44,6 +44,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
@@ -342,14 +343,35 @@
// Raw data associated with the event.
// This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
private final byte[] mData;
+ private final HotwordDetectedResult mHotwordDetectedResult;
+ private final ParcelFileDescriptor mAudioStream;
private EventPayload(boolean triggerAvailable, boolean captureAvailable,
AudioFormat audioFormat, int captureSession, byte[] data) {
+ this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, null,
+ null);
+ }
+
+ EventPayload(AudioFormat audioFormat, HotwordDetectedResult hotwordDetectedResult) {
+ this(false, false, audioFormat, -1, null, hotwordDetectedResult, null);
+ }
+
+ EventPayload(AudioFormat audioFormat,
+ HotwordDetectedResult hotwordDetectedResult,
+ ParcelFileDescriptor audioStream) {
+ this(false, false, audioFormat, -1, null, hotwordDetectedResult, audioStream);
+ }
+
+ private EventPayload(boolean triggerAvailable, boolean captureAvailable,
+ AudioFormat audioFormat, int captureSession, byte[] data,
+ HotwordDetectedResult hotwordDetectedResult, ParcelFileDescriptor audioStream) {
mTriggerAvailable = triggerAvailable;
mCaptureAvailable = captureAvailable;
mCaptureSession = captureSession;
mAudioFormat = audioFormat;
mData = data;
+ mHotwordDetectedResult = hotwordDetectedResult;
+ mAudioStream = audioStream;
}
/**
@@ -405,6 +427,33 @@
return null;
}
}
+
+ /**
+ * Returns {@link HotwordDetectedResult} associated with the hotword event, passed from
+ * {@link HotwordDetectionService}.
+ */
+ @Nullable
+ public HotwordDetectedResult getHotwordDetectedResult() {
+ return mHotwordDetectedResult;
+ }
+
+ /**
+ * Returns a stream with bytes corresponding to the open audio stream with hotword data.
+ *
+ * <p>This data represents an audio stream in the format returned by
+ * {@link #getCaptureAudioFormat}.
+ *
+ * <p>Clients are expected to start consuming the stream within 1 second of receiving the
+ * event.
+ *
+ * <p>When this method returns a non-null, clients must close this stream when it's no
+ * longer needed. Failing to do so will result in microphone being open for longer periods
+ * of time, and app being attributed for microphone usage.
+ */
+ @Nullable
+ public ParcelFileDescriptor getAudioStream() {
+ return mAudioStream;
+ }
}
/**
@@ -508,7 +557,7 @@
mTargetSdkVersion = targetSdkVersion;
mSupportHotwordDetectionService = supportHotwordDetectionService;
if (mSupportHotwordDetectionService) {
- setHotwordDetectionServiceConfig(options, sharedMemory);
+ updateState(options, sharedMemory);
}
try {
Identity identity = new Identity();
@@ -524,30 +573,28 @@
/**
* Set configuration and pass read-only data to hotword detection service.
*
- * @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * @param options Application configuration data to provide to the
+ * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
* other contents that can be used to communicate with other processes.
- * @param sharedMemory The unrestricted data blob provided by the
- * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
+ * @param sharedMemory The unrestricted data blob to provide to the
+ * {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
*
- * @throws IllegalStateException if it doesn't support hotword detection service.
- *
- * @hide
+ * @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a
+ * {@link HotwordDetectionService} when it was created.
*/
- public final void setHotwordDetectionServiceConfig(@Nullable PersistableBundle options,
+ public final void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
if (DBG) {
- Slog.d(TAG, "setHotwordDetectionServiceConfig()");
+ Slog.d(TAG, "updateState()");
}
if (!mSupportHotwordDetectionService) {
throw new IllegalStateException(
- "setHotwordDetectionServiceConfig called, but it doesn't support hotword"
- + " detection service");
+ "updateState called, but it doesn't support hotword detection service");
}
try {
- mModelManagementService.setHotwordDetectionServiceConfig(options, sharedMemory);
+ mModelManagementService.updateState(options, sharedMemory);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 686268c..db984c2 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -82,10 +82,10 @@
}
@Override
- public void setConfig(PersistableBundle options, SharedMemory sharedMemory)
+ public void updateState(PersistableBundle options, SharedMemory sharedMemory)
throws RemoteException {
if (DBG) {
- Log.d(TAG, "#setConfig");
+ Log.d(TAG, "#updateState");
}
mHandler.sendMessage(obtainMessage(HotwordDetectionService::onUpdateState,
HotwordDetectionService.this,
@@ -139,14 +139,14 @@
/**
* Called when the {@link VoiceInteractionService#createAlwaysOnHotwordDetector(String, Locale,
* PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or
- * {@link AlwaysOnHotwordDetector#setHotwordDetectionServiceConfig(PersistableBundle,
- * SharedMemory)} requests an update of the hotword detection parameters.
+ * {@link AlwaysOnHotwordDetector#updateState(PersistableBundle, SharedMemory)} requests an
+ * update of the hotword detection parameters.
*
- * @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * @param options Application configuration data to provide to the
+ * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
* other contents that can be used to communicate with other processes.
- * @param sharedMemory The unrestricted data blob provided by the
- * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
+ * @param sharedMemory The unrestricted data blob to provide to the
+ * {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
*
* @hide
diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/IHotwordDetectionService.aidl
index 8d01dd1..0791f1c 100644
--- a/core/java/android/service/voice/IHotwordDetectionService.aidl
+++ b/core/java/android/service/voice/IHotwordDetectionService.aidl
@@ -34,5 +34,5 @@
long timeoutMillis,
in IDspHotwordDetectionCallback callback);
- void setConfig(in PersistableBundle options, in SharedMemory sharedMemory);
+ void updateState(in PersistableBundle options, in SharedMemory sharedMemory);
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index d47ae27..1da7dc4 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -343,11 +343,14 @@
/**
* Listen for display info changed event.
*
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
- * READ_PHONE_STATE} or that the calling app has carrier privileges (see
- * {@link TelephonyManager#hasCarrierPrivileges}).
+ * For clients compiled on Android 11 SDK, requires permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling app has carrier
+ * privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ * For clients compiled on Android 12 SDK or newer,
+ * {@link android.Manifest.permission#READ_PHONE_STATE} or carrier privileges is not required
+ * anymore.
*
- * @see #onDisplayInfoChanged
+ * @see #onDisplayInfoChanged
* @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead.
*/
@Deprecated
@@ -981,8 +984,12 @@
* <p> The {@link TelephonyDisplayInfo} contains status information shown to the user based on
* carrier policy.
*
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling
- * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ * For clients compiled on Android 11 SDK, requires permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling app has carrier
+ * privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ * For clients compiled on Android 12 SDK or newer,
+ * {@link android.Manifest.permission#READ_PHONE_STATE} or carrier privileges is not required
+ * anymore.
*
* @param telephonyDisplayInfo The display information.
* @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead.
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index d000000..18949cd 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -1057,7 +1057,6 @@
*
* @param telephonyDisplayInfo The display information.
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo);
}
diff --git a/core/java/android/uwb/AngleMeasurement.java b/core/java/android/uwb/AngleMeasurement.java
index 8c771ba..3d60373 100644
--- a/core/java/android/uwb/AngleMeasurement.java
+++ b/core/java/android/uwb/AngleMeasurement.java
@@ -48,7 +48,10 @@
* @throws IllegalArgumentException if the radians, errorRadians, or confidenceLevel is out of
* allowed range
*/
- public AngleMeasurement(double radians, double errorRadians, double confidenceLevel) {
+ public AngleMeasurement(
+ @FloatRange(from = -Math.PI, to = +Math.PI) double radians,
+ @FloatRange(from = 0.0, to = +Math.PI) double errorRadians,
+ @FloatRange(from = 0.0, to = 1.0) double confidenceLevel) {
if (radians < -Math.PI || radians > Math.PI) {
throw new IllegalArgumentException("Invalid radians: " + radians);
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 98b7dbf..8db6456 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -64,8 +64,14 @@
}
@Override
- void hide(boolean animationFinished, @AnimationType int animationType) {
+ public void hide() {
super.hide();
+ mIsRequestedVisibleAwaitingControl = false;
+ }
+
+ @Override
+ void hide(boolean animationFinished, @AnimationType int animationType) {
+ hide();
if (animationFinished) {
// remove IME surface as IME has finished hide animation.
@@ -122,6 +128,9 @@
hide();
removeSurface();
}
+ if (control != null) {
+ mIsRequestedVisibleAwaitingControl = false;
+ }
}
@Override
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6801c27..dea32cd 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.hardware.input.InputManager.APP_USES_RAW_INPUT_COORDS;
import static android.view.Display.DEFAULT_DISPLAY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -23,6 +24,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.compat.Compatibility;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Matrix;
import android.os.Build;
@@ -2672,6 +2674,7 @@
* @see #AXIS_X
*/
public final float getRawX() {
+ Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS);
return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}
@@ -2685,6 +2688,7 @@
* @see #AXIS_Y
*/
public final float getRawY() {
+ Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS);
return nativeGetRawAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
}
@@ -2701,6 +2705,7 @@
* @see #AXIS_X
*/
public float getRawX(int pointerIndex) {
+ Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS);
return nativeGetRawAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
}
@@ -2717,6 +2722,7 @@
* @see #AXIS_Y
*/
public float getRawY(int pointerIndex) {
+ Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS);
return nativeGetRawAxisValue(mNativePtr, AXIS_Y, pointerIndex, HISTORY_CURRENT);
}
diff --git a/core/java/android/view/SoundEffectConstants.java b/core/java/android/view/SoundEffectConstants.java
index f177451..bd86a47 100644
--- a/core/java/android/view/SoundEffectConstants.java
+++ b/core/java/android/view/SoundEffectConstants.java
@@ -16,11 +16,14 @@
package android.view;
+import android.annotation.IntDef;
import android.media.AudioManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Random;
/**
@@ -34,25 +37,55 @@
public static final int CLICK = 0;
+ /** Effect id for a navigation left */
public static final int NAVIGATION_LEFT = 1;
+ /** Effect id for a navigation up */
public static final int NAVIGATION_UP = 2;
+ /** Effect id for a navigation right */
public static final int NAVIGATION_RIGHT = 3;
+ /** Effect id for a navigation down */
public static final int NAVIGATION_DOWN = 4;
- /** Sound effect for a repeatedly triggered navigation, e.g. due to long pressing a button */
+ /** Effect id for a repeatedly triggered navigation left, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_LEFT = 5;
- /** @see #NAVIGATION_REPEAT_LEFT */
+ /** Effect id for a repeatedly triggered navigation up, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_UP = 6;
- /** @see #NAVIGATION_REPEAT_LEFT */
+ /** Effect id for a repeatedly triggered navigation right, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_RIGHT = 7;
- /** @see #NAVIGATION_REPEAT_LEFT */
+ /** Effect id for a repeatedly triggered navigation down, e.g. due to long pressing a button */
public static final int NAVIGATION_REPEAT_DOWN = 8;
+ /** @hide */
+ @IntDef(value = {
+ CLICK,
+ NAVIGATION_LEFT,
+ NAVIGATION_UP,
+ NAVIGATION_RIGHT,
+ NAVIGATION_DOWN,
+ NAVIGATION_REPEAT_LEFT,
+ NAVIGATION_REPEAT_UP,
+ NAVIGATION_REPEAT_RIGHT,
+ NAVIGATION_REPEAT_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SoundEffect {}
+
+ /** @hide */
+ @IntDef(prefix = { "NAVIGATION_" }, value = {
+ NAVIGATION_LEFT,
+ NAVIGATION_UP,
+ NAVIGATION_RIGHT,
+ NAVIGATION_DOWN,
+ NAVIGATION_REPEAT_LEFT,
+ NAVIGATION_REPEAT_UP,
+ NAVIGATION_REPEAT_RIGHT,
+ NAVIGATION_REPEAT_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NavigationSoundEffect {}
+
/**
* Get the sonification constant for the focus directions.
- * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
- * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}
- * or {@link View#FOCUS_BACKWARD}
-
+ * @param direction The direction of the focus.
* @return The appropriate sonification constant.
* @throws {@link IllegalArgumentException} when the passed direction is not one of the
* documented values.
@@ -76,16 +109,14 @@
/**
* Get the sonification constant for the focus directions
- * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
- * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}
- * or {@link View#FOCUS_BACKWARD}
+ * @param direction The direction of the focus.
* @param repeating True if the user long-presses a direction
* @return The appropriate sonification constant
* @throws IllegalArgumentException when the passed direction is not one of the
* documented values.
*/
- public static int getConstantForFocusDirection(@View.FocusDirection int direction,
- boolean repeating) {
+ public static @NavigationSoundEffect int getConstantForFocusDirection(
+ @View.FocusDirection int direction, boolean repeating) {
if (repeating) {
switch (direction) {
case View.FOCUS_RIGHT:
@@ -112,7 +143,7 @@
* @hide
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
- public static boolean isNavigationRepeat(int effectId) {
+ public static boolean isNavigationRepeat(@NavigationSoundEffect int effectId) {
return effectId == SoundEffectConstants.NAVIGATION_REPEAT_DOWN
|| effectId == SoundEffectConstants.NAVIGATION_REPEAT_LEFT
|| effectId == SoundEffectConstants.NAVIGATION_REPEAT_RIGHT
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f1eef9f..82106b0 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1389,6 +1389,8 @@
// If we are using BLAST, merge the transaction with the viewroot buffer transaction.
viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber);
return;
+ } else {
+ mRtTransaction.apply();
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7455b8b..a03e9e3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -151,6 +151,8 @@
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
import android.view.inspector.InspectableProperty.FlagEntry;
+import android.view.translation.TranslationSpec.DataFormat;
+import android.view.translation.ViewTranslationCallback;
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.Checkable;
@@ -5253,6 +5255,9 @@
@Nullable
private String[] mOnReceiveContentMimeTypes;
+ @Nullable
+ private ViewTranslationCallback mViewTranslationCallback;
+
/**
* Simple constructor to use when creating a view from code.
*
@@ -26136,9 +26141,9 @@
* <p>The sound effect will only be played if sound effects are enabled by the user, and
* {@link #isSoundEffectsEnabled()} is true.
*
- * @param soundConstant One of the constants defined in {@link SoundEffectConstants}
+ * @param soundConstant One of the constants defined in {@link SoundEffectConstants}.
*/
- public void playSoundEffect(int soundConstant) {
+ public void playSoundEffect(@SoundEffectConstants.SoundEffect int soundConstant) {
if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
return;
}
@@ -30717,71 +30722,62 @@
}
}
+ //TODO(b/1960696): update javadoc when dispatchRequestTranslation is ready.
/**
- * Returns a {@link ViewTranslationRequest} to the {@link onStartUiTranslation} which represents
- * the content to be translated.
+ * Returns a {@link ViewTranslationRequest} which represents the content to be translated.
*
- * <p>The default implementation does nothing and return null.</p>
+ * <p>The default implementation does nothing and returns null.</p>
*
- * @hide
- *
- * @return the {@link ViewTranslationRequest} which contains the information to be translated.
+ * @param supportedFormats the supported translation formats. For now, the only possible value
+ * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+ * @return the {@link ViewTranslationRequest} which contains the information to be translated or
+ * {@code null} if this View doesn't support translation.
+ * The {@link AutofillId} must be set on the returned value.
*/
@Nullable
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public ViewTranslationRequest onCreateTranslationRequest() {
+ public ViewTranslationRequest createTranslationRequest(
+ @NonNull @DataFormat int[] supportedFormats) {
return null;
}
/**
- * Called when the user wants to show the original text instead of the translated text.
+ * Returns a {@link ViewTranslationCallback} that is used to display/hide the translated
+ * information. If the View supports displaying translated content, it should implement
+ * {@link ViewTranslationCallback}.
*
- * @hide
+ * <p>The default implementation returns null if developers don't set the customized
+ * {@link ViewTranslationCallback} by {@link #setViewTranslationCallback} </p>
*
- * <p> The default implementation does nothing.
+ * @return a {@link ViewTranslationCallback} that is used to control how to display the
+ * translated information or {@code null} if this View doesn't support translation.
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onPauseUiTranslation() {
- // no-op
+ @Nullable
+ public ViewTranslationCallback getViewTranslationCallback() {
+ return mViewTranslationCallback;
}
/**
- * User can switch back to show the original text, this method called when the user wants to
- * re-show the translated text again.
+ * Sets a {@link ViewTranslationCallback} that is used to display/hide the translated
+ * information. Developers can provide the customized implementation for show/hide translated
+ * information.
*
- * @hide
- *
- * <p> The default implementation does nothing.</p>
+ * @param callback a {@link ViewTranslationCallback} that is used to control how to display the
+ * translated information
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onRestoreUiTranslation() {
- // no-op
+ public void setViewTranslationCallback(@NonNull ViewTranslationCallback callback) {
+ mViewTranslationCallback = callback;
}
/**
- * Called when the user finish the Ui translation and no longer to show the translated text.
- *
- * @hide
- *
- * <p> The default implementation does nothing.</p>
- */
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onFinishUiTranslation() {
- // no-op
- }
-
- /**
- * Called when the request from {@link onStartUiTranslation} is completed by the translation
- * service so that the translation result can be shown.
- *
- * @hide
+ * Called when the content from {@link #createTranslationRequest} had been translated by the
+ * TranslationService.
*
* <p> The default implementation does nothing.</p>
*
- * @param response the translated information which can be shown in the view.
+ * @param response a {@link ViewTranslationResponse} that contains the translated information
+ * which can be shown in the view.
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onTranslationComplete(@NonNull ViewTranslationResponse response) {
+ public void onTranslationResponse(@NonNull ViewTranslationResponse response) {
// no-op
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index dbccf10..0a246a6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4293,7 +4293,7 @@
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
boolean useAsyncReport = false;
- if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
+ if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
if (isHardwareEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
@@ -7754,7 +7754,7 @@
* {@inheritDoc}
*/
@Override
- public void playSoundEffect(int effectId) {
+ public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) {
checkThread();
try {
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 2b12230..ce01469 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -143,6 +143,9 @@
private @Nullable ContentCaptureContext mClientContext;
private @Nullable Insets mInsets;
+ /** Only used in the main Content Capture session, no need to parcel */
+ private boolean mTextHasComposingSpan;
+
/** @hide */
public ContentCaptureEvent(int sessionId, int type, long eventTime) {
mSessionId = sessionId;
@@ -243,11 +246,21 @@
/** @hide */
@NonNull
- public ContentCaptureEvent setText(@Nullable CharSequence text) {
+ public ContentCaptureEvent setText(@Nullable CharSequence text, boolean hasComposingSpan) {
mText = text;
+ mTextHasComposingSpan = hasComposingSpan;
return this;
}
+ /**
+ * The value is not parcelled, become false after parcelled.
+ * @hide
+ */
+ @NonNull
+ public boolean getTextHasComposingSpan() {
+ return mTextHasComposingSpan;
+ }
+
/** @hide */
@NonNull
public ContentCaptureEvent setInsets(@NonNull Insets insets) {
@@ -361,7 +374,7 @@
throw new IllegalArgumentException("mergeEvent(): got "
+ "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event);
} else if (eventType == TYPE_VIEW_TEXT_CHANGED) {
- setText(event.getText());
+ setText(event.getText(), event.getTextHasComposingSpan());
} else {
Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType)
+ ") does not support this event type.");
@@ -479,7 +492,7 @@
if (node != null) {
event.setViewNode(node);
}
- event.setText(parcel.readCharSequence());
+ event.setText(parcel.readCharSequence(), false);
if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
event.setParentSessionId(parcel.readInt());
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 5ca793e..f196f75 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -43,12 +43,15 @@
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
+import android.text.Spannable;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.Log;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.inputmethod.BaseInputConnection;
import com.android.internal.os.IResultReceiver;
@@ -57,6 +60,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -147,6 +151,12 @@
private final LocalLog mFlushHistory;
/**
+ * If the event in the buffer is of type {@link TYPE_VIEW_TEXT_CHANGED}, this value
+ * indicates whether the event has composing span or not.
+ */
+ private final Map<AutofillId, Boolean> mLastComposingSpan = new ArrayMap<>();
+
+ /**
* Binder object used to update the session state.
*/
@NonNull
@@ -335,26 +345,47 @@
// Some type of events can be merged together
boolean addEvent = true;
- if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) {
- final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
+ if (eventType == TYPE_VIEW_TEXT_CHANGED) {
+ // We determine whether to add or merge the current event by following criteria:
+ // 1. Don't have composing span: always add.
+ // 2. Have composing span:
+ // 2.1 either last or current text is empty: add.
+ // 2.2 last event doesn't have composing span: add.
+ // Otherwise, merge.
- // We merge two consecutive text change event, unless one of them clears the text.
- if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
- && lastEvent.getId().equals(event.getId())) {
- boolean bothNonEmpty = !TextUtils.isEmpty(lastEvent.getText())
- && !TextUtils.isEmpty(event.getText());
- boolean equalContent = TextUtils.equals(lastEvent.getText(), event.getText());
- if (equalContent) {
- addEvent = false;
- } else if (bothNonEmpty) {
- lastEvent.mergeEvent(event);
- addEvent = false;
- }
- if (!addEvent && sVerbose) {
- Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
- + getSanitizedString(event.getText()));
+ final CharSequence text = event.getText();
+ final boolean textHasComposingSpan = event.getTextHasComposingSpan();
+
+ if (textHasComposingSpan && !mLastComposingSpan.isEmpty()) {
+ final Boolean lastEventHasComposingSpan = mLastComposingSpan.get(event.getId());
+ if (lastEventHasComposingSpan != null && lastEventHasComposingSpan.booleanValue()) {
+ ContentCaptureEvent lastEvent = null;
+ for (int index = mEvents.size() - 1; index >= 0; index--) {
+ final ContentCaptureEvent tmpEvent = mEvents.get(index);
+ if (event.getId().equals(tmpEvent.getId())) {
+ lastEvent = tmpEvent;
+ break;
+ }
+ }
+ if (lastEvent != null) {
+ final CharSequence lastText = lastEvent.getText();
+ final boolean bothNonEmpty = !TextUtils.isEmpty(lastText)
+ && !TextUtils.isEmpty(text);
+ boolean equalContent = TextUtils.equals(lastText, text);
+ if (equalContent) {
+ addEvent = false;
+ } else if (bothNonEmpty && lastEventHasComposingSpan) {
+ lastEvent.mergeEvent(event);
+ addEvent = false;
+ }
+ if (!addEvent && sVerbose) {
+ Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+ + getSanitizedString(text));
+ }
+ }
}
}
+ mLastComposingSpan.put(event.getId(), textHasComposingSpan);
}
if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
@@ -374,6 +405,11 @@
mEvents.add(event);
}
+ // TODO: we need to change when the flush happens so that we don't flush while the
+ // composing span hasn't changed. But we might need to keep flushing the events for the
+ // non-editable views and views that don't have the composing state; otherwise some other
+ // Content Capture features may be delayed.
+
final int numberEvents = mEvents.size();
final boolean bufferEvent = numberEvents < maxBufferSize;
@@ -550,6 +586,7 @@
? Collections.emptyList()
: mEvents;
mEvents = null;
+ mLastComposingSpan.clear();
return new ParceledListSlice<>(events);
}
@@ -677,9 +714,16 @@
}
void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+ // Since the same CharSequence instance may be reused in the TextView, we need to make
+ // a copy of its content so that its value will not be changed by subsequent updates
+ // in the TextView.
+ final String eventText = text == null ? null : text.toString();
+ final boolean textHasComposingSpan =
+ text instanceof Spannable && BaseInputConnection.getComposingSpanStart(
+ (Spannable) text) >= 0;
mHandler.post(() -> sendEvent(
new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
- .setAutofillId(id).setText(text)));
+ .setAutofillId(id).setText(eventText, textHasComposingSpan)));
}
/** Public because is also used by ViewRootImpl */
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index d79ecca..15d01ae 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -46,7 +46,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
/**
* A controller to manage the ui translation requests for the {@link Activity}.
@@ -77,6 +77,7 @@
private final HandlerThread mWorkerThread;
@NonNull
private final Handler mWorkerHandler;
+ private int mCurrentState;
public UiTranslationController(Activity activity, Context context) {
mActivity = activity;
@@ -101,6 +102,9 @@
}
Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
+ (DEBUG ? ", views: " + views : ""));
+ synchronized (mLock) {
+ mCurrentState = state;
+ }
switch (state) {
case STATE_UI_TRANSLATION_STARTED:
final Pair<TranslationSpec, TranslationSpec> specs =
@@ -114,14 +118,14 @@
}
break;
case STATE_UI_TRANSLATION_PAUSED:
- runForEachView(View::onPauseUiTranslation);
+ runForEachView((view, callback) -> callback.onHideTranslation(view));
break;
case STATE_UI_TRANSLATION_RESUMED:
- runForEachView(View::onRestoreUiTranslation);
+ runForEachView((view, callback) -> callback.onShowTranslation(view));
break;
case STATE_UI_TRANSLATION_FINISHED:
destroyTranslators();
- runForEachView(View::onFinishUiTranslation);
+ runForEachView((view, callback) -> callback.onClearTranslation(view));
synchronized (mLock) {
mViews.clear();
}
@@ -232,11 +236,16 @@
}
final SparseArray<ViewTranslationResponse> translatedResult =
response.getViewTranslationResponses();
+ // TODO(b/177960696): handle virtual views. Check the result if the AutofillId is virtual
+ // AutofillId?
onTranslationCompleted(translatedResult);
}
private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
if (!mActivity.isResumed()) {
+ if (DEBUG) {
+ Log.v(TAG, "onTranslationCompleted: Activity is not resumed.");
+ }
return;
}
final int resultCount = translatedResult.size();
@@ -244,6 +253,11 @@
Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses.");
}
synchronized (mLock) {
+ if (mCurrentState == STATE_UI_TRANSLATION_FINISHED) {
+ Log.w(TAG, "onTranslationCompleted: the translation state is finished now. "
+ + "Skip to show the translated text.");
+ return;
+ }
for (int i = 0; i < resultCount; i++) {
final ViewTranslationResponse response = translatedResult.get(i);
final AutofillId autofillId = response.getAutofillId();
@@ -256,18 +270,28 @@
+ " may be gone.");
continue;
}
- mActivity.runOnUiThread(() -> view.onTranslationComplete(response));
+ mActivity.runOnUiThread(() -> {
+ if (view.getViewTranslationCallback() == null) {
+ if (DEBUG) {
+ Log.d(TAG, view + " doesn't support showing translation because of "
+ + "null ViewTranslationCallback.");
+ }
+ return;
+ }
+ view.onTranslationResponse(response);
+ view.getViewTranslationCallback().onShowTranslation(view);
+ });
}
}
}
/**
- * Called when there is an ui translation request comes to request view translation.
+ * Creates a Translator for the given source and target translation specs and start the ui
+ * translation when the Translator is created successfully.
*/
@WorkerThread
private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec destSpec,
List<AutofillId> views) {
- // Create Translator
final Translator translator = createTranslatorIfNeeded(sourceSpec, destSpec);
if (translator == null) {
Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " destSpec:"
@@ -295,31 +319,51 @@
*/
private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
synchronized (mLock) {
- // Find Views collect the translation data
- final ArrayList<ViewTranslationRequest> requests = new ArrayList<>();
- final ArrayList<View> foundViews = new ArrayList<>();
- findViewsTraversalByAutofillIds(views, foundViews);
- for (int i = 0; i < foundViews.size(); i++) {
- final View view = foundViews.get(i);
- final int currentCount = i;
- mActivity.runOnUiThread(() -> {
- final ViewTranslationRequest request = view.onCreateTranslationRequest();
- if (request != null
- && request.getKeys().size() > 0) {
- requests.add(request);
- }
- if (currentCount == (foundViews.size() - 1)) {
- Log.v(TAG, "onUiTranslationStarted: collect " + requests.size()
- + " requests.");
- mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
- UiTranslationController::sendTranslationRequest,
- UiTranslationController.this, translator, requests));
- }
- });
- }
+ // TODO(b/177960696): handle virtual views. Need to check the requested view list is
+ // virtual AutofillId or not
+ findViewsAndCollectViewTranslationRequest(translator, views);
}
}
+ /**
+ * If the translation requested views are not virtual view, we need to traverse the tree to
+ * find the views and get the View's ViewTranslationRequest.
+ */
+ private void findViewsAndCollectViewTranslationRequest(Translator translator,
+ List<AutofillId> views) {
+ // Find Views collect the translation data
+ final ArrayList<ViewTranslationRequest> requests = new ArrayList<>();
+ final ArrayList<View> foundViews = new ArrayList<>();
+ findViewsTraversalByAutofillIds(views, foundViews);
+ final int[] supportedFormats = getSupportedFormatsLocked();
+ for (int i = 0; i < foundViews.size(); i++) {
+ final View view = foundViews.get(i);
+ final int currentCount = i;
+ mActivity.runOnUiThread(() -> {
+ final ViewTranslationRequest request =
+ view.createTranslationRequest(supportedFormats);
+ // TODO(b/177960696): handle null case, the developers may want to handle the
+ // translation, call dispatchRequestTranslation() instead.
+ if (request != null
+ && request.getKeys().size() > 0) {
+ requests.add(request);
+ }
+ if (currentCount == (foundViews.size() - 1)) {
+ Log.v(TAG, "onUiTranslationStarted: collect " + requests.size()
+ + " requests.");
+ mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
+ UiTranslationController::sendTranslationRequest,
+ UiTranslationController.this, translator, requests));
+ }
+ });
+ }
+ }
+
+ private int[] getSupportedFormatsLocked() {
+ // We only support text now
+ return new int[] {TranslationSpec.DATA_FORMAT_TEXT};
+ }
+
private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds,
ArrayList<View> foundViews) {
final ArrayList<ViewRootImpl> roots =
@@ -356,20 +400,21 @@
}
}
- private void runForEachView(Consumer<View> action) {
+ private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
synchronized (mLock) {
final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
mActivity.runOnUiThread(() -> {
final int viewCounts = views.size();
for (int i = 0; i < viewCounts; i++) {
final View view = views.valueAt(i).get();
- if (view == null) {
+ if (view == null || view.getViewTranslationCallback() == null) {
if (DEBUG) {
- Log.d(TAG, "View was gone for autofillid = " + views.keyAt(i));
+ Log.d(TAG, "View was gone or ViewTranslationCallback for autofillid "
+ + "= " + views.keyAt(i));
}
continue;
}
- action.accept(view);
+ action.accept(view, view.getViewTranslationCallback());
}
});
}
diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java
new file mode 100644
index 0000000..c895984
--- /dev/null
+++ b/core/java/android/view/translation/ViewTranslationCallback.java
@@ -0,0 +1,51 @@
+/*
+ * 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.translation;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.view.View;
+
+/**
+ * Callback for handling the translated information show or hide in the {@link View}. See {@link
+ * View#onTranslationResponse} for how to get the translated information.
+ */
+@UiThread
+public interface ViewTranslationCallback {
+ /**
+ * Called when the translated text is ready to show or if the user has requested to reshow the
+ * translated content after hiding it. This may be called before the translation results are
+ * returned through the {@link View#onTranslationResponse}.
+ *
+ * @return {@code true} if the View handles showing the translation.
+ */
+ boolean onShowTranslation(@NonNull View view);
+ /**
+ * Called when the user wants to show the original text instead of the translated text. This
+ * may be called before the translation results are returned through the
+ * {@link View#onTranslationResponse}.
+ *
+ * @return {@code true} if the View handles hiding the translation.
+ */
+ boolean onHideTranslation(@NonNull View view);
+ /**
+ * Called when the user finish the Ui translation and no longer to show the translated text.
+ *
+ * @return {@code true} if the View handles clearing the translation.
+ */
+ boolean onClearTranslation(@NonNull View view);
+}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index a63305e..287c182 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -20,12 +20,10 @@
import android.text.Editable;
import android.text.Selection;
import android.text.Spanned;
-import android.text.TextUtils;
import android.text.method.WordIterator;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
import android.util.Log;
-import android.util.LruCache;
import android.util.Range;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
@@ -98,10 +96,6 @@
private Runnable mSpellRunnable;
- private static final int SUGGESTION_SPAN_CACHE_SIZE = 10;
- private final LruCache<Long, SuggestionSpan> mSuggestionSpanCache =
- new LruCache<Long, SuggestionSpan>(SUGGESTION_SPAN_CACHE_SIZE);
-
public SpellChecker(TextView textView) {
mTextView = textView;
@@ -144,7 +138,6 @@
// Remove existing misspelled SuggestionSpans
mTextView.removeMisspelledSpans((Editable) mTextView.getText());
- mSuggestionSpanCache.evictAll();
}
private void setLocale(Locale locale) {
@@ -410,16 +403,7 @@
}
if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
&& end > start) {
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Remove existing misspelled span. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
- mSuggestionSpanCache.remove(key);
- }
+ removeErrorSuggestionSpan(editable, start, end, RemoveReason.OBSOLETE);
}
}
return spellCheckSpan;
@@ -428,6 +412,35 @@
return null;
}
+ private enum RemoveReason {
+ /**
+ * Indicates the previous SuggestionSpan is replaced by a new SuggestionSpan.
+ */
+ REPLACE,
+ /**
+ * Indicates the previous SuggestionSpan is removed because corresponding text is
+ * considered as valid words now.
+ */
+ OBSOLETE,
+ }
+
+ private static void removeErrorSuggestionSpan(
+ Editable editable, int start, int end, RemoveReason reason) {
+ SuggestionSpan[] spans = editable.getSpans(start, end, SuggestionSpan.class);
+ for (SuggestionSpan span : spans) {
+ if (editable.getSpanStart(span) == start
+ && editable.getSpanEnd(span) == end
+ && (span.getFlags() & (SuggestionSpan.FLAG_MISSPELLED
+ | SuggestionSpan.FLAG_GRAMMAR_ERROR)) != 0) {
+ if (DBG) {
+ Log.i(TAG, "Remove existing misspelled/grammar error span on "
+ + editable.subSequence(start, end) + ", reason: " + reason);
+ }
+ editable.removeSpan(span);
+ }
+ }
+ }
+
@Override
public void onGetSuggestions(SuggestionsInfo[] results) {
final Editable editable = (Editable) mTextView.getText();
@@ -543,16 +556,7 @@
}
SuggestionSpan suggestionSpan =
new SuggestionSpan(mTextView.getContext(), suggestions, flags);
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Cached span on the same position is cleard. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
- }
- mSuggestionSpanCache.put(key, suggestionSpan);
+ removeErrorSuggestionSpan(editable, start, end, RemoveReason.REPLACE);
editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 940a3c9..6733c0d3 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -195,7 +195,9 @@
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.view.translation.TranslationRequestValue;
+import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationController;
+import android.view.translation.ViewTranslationCallback;
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.RemoteViews.RemoteView;
@@ -203,6 +205,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
@@ -737,7 +740,7 @@
private MovementMethod mMovement;
private TransformationMethod mTransformation;
- private TranslationTransformationMethod mTranslationTransformation;
+ private TextViewTranslationCallback mDefaultTranslationCallback;
@UnsupportedAppUsage
private boolean mAllowTransformationLengthChange;
@UnsupportedAppUsage
@@ -13857,136 +13860,104 @@
}
/**
- * Provides a {@link ViewTranslationRequest} that represents the content to be translated via
- * translation service.
+ * Returns a {@link ViewTranslationRequest} which represents the content to be translated.
*
- * <p>NOTE: When overriding the method, it should not translate the password. We also suggest
- * that not translating the text is selectable or editable. We use the transformation method to
- * implement showing the translated text. The TextView does not support the transformation
- * method text length change. If the text is selectable or editable, it will crash while
- * selecting the text. To support it, it needs broader changes to text APIs, we only allow to
- * translate non selectable and editable text now.
+ * <p>NOTE: When overriding the method, it should not translate the password. If the subclass
+ * uses {@link TransformationMethod} to display the translated result, it's also not recommend
+ * to translate text is selectable or editable.
*
- * @hide
+ * @param supportedFormats the supported translation format. The value could be {@link
+ * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+ * @return the {@link ViewTranslationRequest} which contains the information to be translated.
*/
@Nullable
@Override
- public ViewTranslationRequest onCreateTranslationRequest() {
- if (mText == null || mText.length() == 0) {
+ public ViewTranslationRequest createTranslationRequest(@NonNull int[] supportedFormats) {
+ if (supportedFormats == null || supportedFormats.length == 0) {
// TODO(b/182433547): remove before S release
if (UiTranslationController.DEBUG) {
- Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
+ Log.w(LOG_TAG, "Do not provide the support translation formats.");
}
return null;
}
- // Not translate password, editable text and not important for translation
- // TODO(b/177214256): support selectable text translation. It needs to broader changes to
- // text selection apis, not support in S.
- boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
- if (isTextEditable() || isPassword || isTextSelectable()) {
- // TODO(b/182433547): remove before S release
- if (UiTranslationController.DEBUG) {
- Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable()
- + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable());
+ ViewTranslationRequest.Builder requestBuilder =
+ new ViewTranslationRequest.Builder(getAutofillId());
+ // Support Text translation
+ if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
+ if (mText == null || mText.length() == 0) {
+ // TODO(b/182433547): remove before S release
+ if (UiTranslationController.DEBUG) {
+ Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
+ }
+ return null;
}
- return null;
+ boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
+ // TODO(b/177214256): support selectable text translation.
+ // We use the TransformationMethod to implement showing the translated text. The
+ // TextView does not support the text length change for TransformationMethod. If the
+ // text is selectable or editable, it will crash while selecting the text. To support
+ // it, it needs broader changes to text APIs, we only allow to translate non selectable
+ // and editable text in S.
+ if (isTextEditable() || isPassword || isTextSelectable()) {
+ // TODO(b/182433547): remove before S release
+ if (UiTranslationController.DEBUG) {
+ Log.w(LOG_TAG, "Cannot create translation request. editable = "
+ + isTextEditable() + ", isPassword = " + isPassword + ", selectable = "
+ + isTextSelectable());
+ }
+ return null;
+ }
+ // TODO(b/176488462): apply the view's important for translation
+ requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
+ TranslationRequestValue.forText(mText));
}
- // TODO(b/176488462): apply the view's important for translation property
- // TODO(b/174283799): remove the spans from the mText and save the spans information
- // TODO: use fixed ids for request texts.
- ViewTranslationRequest request =
- new ViewTranslationRequest.Builder(getAutofillId())
- .setValue(ViewTranslationRequest.ID_TEXT,
- TranslationRequestValue.forText(mText))
- .build();
- return request;
+ return requestBuilder.build();
}
/**
- * Provides the implementation that pauses the ongoing Ui translation, it will show the original
- * text instead of the translated text and restore the original transformation method.
+ * Returns a {@link ViewTranslationCallback} that is used to display the translated information.
+ * The default implementation will use a {@link TransformationMethod} that allow to replace the
+ * current {@link TransformationMethod} to transform the original text to the translated text
+ * display.
*
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
- *
- * @hide
+ * @return a {@link ViewTranslationCallback} that is used to control how to display the
+ * translated information or {@code null} if this View doesn't support translation.
*/
+ @Nullable
@Override
- public void onPauseUiTranslation() {
- // Restore to original text content.
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod());
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onPauseUiTranslation(): no translated text.");
+ public ViewTranslationCallback getViewTranslationCallback() {
+ return getDefaultViewTranslationCallback();
+ }
+
+ private ViewTranslationCallback getDefaultViewTranslationCallback() {
+ if (mDefaultTranslationCallback == null) {
+ mDefaultTranslationCallback = new TextViewTranslationCallback();
}
+ return mDefaultTranslationCallback;
}
/**
- * Provides the implementation that restoes the paused Ui translation, it will show the
- * translated text again if the text had been translated. This method will replace the current
- * tansformation method with {@link TranslationTransformationMethod}.
*
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
+ * Called when the content from {@link #createTranslationRequest} had been translated by the
+ * TranslationService. The default implementation will replace the current
+ * {@link TransformationMethod} to transform the original text to the translated text display.
*
- * @hide
+ * @param response a {@link ViewTranslationResponse} that contains the translated information
+ * which can be shown in the view.
*/
@Override
- public void onRestoreUiTranslation() {
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation);
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text.");
- }
- }
-
- /**
- * Provides the implementation that finishes the current Ui translation and it's no longer to
- * show the translated text. This method restores the original transformation method and resets
- * the saved {@link TranslationTransformationMethod}.
- *
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
- *
- * @hide
- */
- @Override
- public void onFinishUiTranslation() {
- // Restore to original text content and clear TranslationTransformation
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod());
- mTranslationTransformation = null;
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onFinishUiTranslation(): no translated text.");
- }
- }
-
- /**
- * Default {@link TextView} implementation after the translation request is done by the
- * translation service, it's ok to show the translated text. This method will save the original
- * transformation method and replace the current transformation method with
- * {@link TranslationTransformationMethod}.
- *
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}
- * should also be overridden.
- *
- * @hide
- */
- @Override
- public void onTranslationComplete(@NonNull ViewTranslationResponse response) {
- // Show the translated text.
- TransformationMethod originalTranslationMethod = mTranslationTransformation != null
- ? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation;
- mTranslationTransformation =
+ public void onTranslationResponse(@NonNull ViewTranslationResponse response) {
+ // TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default
+ // implementation if the view has overridden getViewTranslationCallback.
+ TextViewTranslationCallback callback =
+ (TextViewTranslationCallback) getDefaultViewTranslationCallback();
+ TranslationTransformationMethod oldTranslationMethod =
+ callback.getTranslationTransformation();
+ TransformationMethod originalTranslationMethod = oldTranslationMethod != null
+ ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation;
+ TranslationTransformationMethod newTranslationMethod =
new TranslationTransformationMethod(response, originalTranslationMethod);
// TODO(b/178353965): well-handle setTransformationMethod.
- setTransformationMethod(mTranslationTransformation);
+ callback.setTranslationTransformation(newTranslationMethod);
}
}
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
new file mode 100644
index 0000000..296d93c
--- /dev/null
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -0,0 +1,120 @@
+/*
+ * 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.widget;
+
+import android.annotation.NonNull;
+import android.text.method.TranslationTransformationMethod;
+import android.util.Log;
+import android.view.View;
+import android.view.translation.UiTranslationManager;
+import android.view.translation.ViewTranslationCallback;
+import android.view.translation.ViewTranslationResponse;
+
+/**
+ * Default implementation for {@link ViewTranslationCallback} for {@link TextView} components.
+ * This class handles how to display the translated information for {@link TextView}.
+ *
+ * @hide
+ */
+public class TextViewTranslationCallback implements ViewTranslationCallback {
+
+ private static final String TAG = "TextViewTranslationCallback";
+
+ private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
+
+ private TranslationTransformationMethod mTranslationTransformation;
+
+ /**
+ * Invoked by the platform when receiving the successful {@link ViewTranslationResponse} for the
+ * view that provides the translatable information by {@link View#createTranslationRequest} and
+ * sent by the platform.
+ */
+ void setTranslationTransformation(TranslationTransformationMethod method) {
+ if (method == null) {
+ if (DEBUG) {
+ Log.w(TAG, "setTranslationTransformation: should not set null "
+ + "TranslationTransformationMethod");
+ }
+ return;
+ }
+ mTranslationTransformation = method;
+ }
+
+ TranslationTransformationMethod getTranslationTransformation() {
+ return mTranslationTransformation;
+ }
+
+ private void clearTranslationTransformation() {
+ if (DEBUG) {
+ Log.v(TAG, "clearTranslationTransformation: " + mTranslationTransformation);
+ }
+ mTranslationTransformation = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onShowTranslation(@NonNull View view) {
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(mTranslationTransformation);
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onShowTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onHideTranslation(@NonNull View view) {
+ // Restore to original text content.
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(
+ mTranslationTransformation.getOriginalTransformationMethod());
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onHideTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onClearTranslation(@NonNull View view) {
+ // Restore to original text content and clear TranslationTransformation
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(
+ mTranslationTransformation.getOriginalTransformationMethod());
+ clearTranslationTransformation();
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onClearTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 922f96e..3b6a877 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -221,32 +221,19 @@
@Override
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- if (mIsSendAction) {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_personal_apps_available_share,
- /* subtitleRes */ 0);
- } else {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_personal_apps_available_resolve,
- /* subtitleRes */ 0);
- }
+ showEmptyState(listAdapter,
+ R.drawable.ic_no_apps,
+ R.string.resolver_no_personal_apps_available,
+ /* subtitleRes */ 0);
+
}
@Override
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- if (mIsSendAction) {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_work_apps_available_share,
- /* subtitleRes */ 0);
- } else {
- showEmptyState(listAdapter,
- R.drawable.ic_no_apps,
- R.string.resolver_no_work_apps_available_resolve,
- /* subtitleRes */ 0);
- }
+ showEmptyState(listAdapter,
+ R.drawable.ic_no_apps,
+ R.string.resolver_no_work_apps_available,
+ /* subtitleRes */ 0);
}
void setEmptyStateBottomOffset(int bottomOffset) {
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 2a022e6..e273286 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -229,15 +229,14 @@
/**
* Set configuration and pass read-only data to hotword detection service.
*
- * @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * @param options Application configuration data to provide to the
+ * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
* other contents that can be used to communicate with other processes.
- * @param sharedMemory The unrestricted data blob provided by the
- * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
+ * @param sharedMemory The unrestricted data blob to provide to the
+ * {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
*/
- void setHotwordDetectionServiceConfig(
- in PersistableBundle options, in SharedMemory sharedMemory);
+ void updateState(in PersistableBundle options, in SharedMemory sharedMemory);
/**
* Requests to shutdown hotword detection service.
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index a2f014c..622f166 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -221,7 +221,7 @@
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
showEmptyState(listAdapter,
R.drawable.ic_no_apps,
- R.string.resolver_no_personal_apps_available_resolve,
+ R.string.resolver_no_personal_apps_available,
/* subtitleRes */ 0);
}
@@ -229,7 +229,7 @@
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
showEmptyState(listAdapter,
R.drawable.ic_no_apps,
- R.string.resolver_no_work_apps_available_resolve,
+ R.string.resolver_no_work_apps_available,
/* subtitleRes */ 0);
}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 762297d..86f29a8 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -106,13 +106,17 @@
}
private String resolveTitle() {
- final int titleId = (mSuppliedDialogInfo != null) ? mSuppliedDialogInfo.getTitleResId()
- : ID_NULL;
- if (titleId != ID_NULL && mSuspendingAppResources != null) {
- try {
- return mSuspendingAppResources.getString(titleId);
- } catch (Resources.NotFoundException nfe) {
- Slog.e(TAG, "Could not resolve string resource id " + titleId);
+ if (mSuppliedDialogInfo != null) {
+ final int titleId = mSuppliedDialogInfo.getTitleResId();
+ final String title = mSuppliedDialogInfo.getTitle();
+ if (titleId != ID_NULL && mSuspendingAppResources != null) {
+ try {
+ return mSuspendingAppResources.getString(titleId);
+ } catch (Resources.NotFoundException nfe) {
+ Slog.e(TAG, "Could not resolve string resource id " + titleId);
+ }
+ } else if (title != null) {
+ return title;
}
}
return getString(R.string.app_suspended_title);
@@ -159,13 +163,17 @@
Slog.w(TAG, "Unknown neutral button action: " + mNeutralButtonAction);
return null;
}
- final int buttonTextId = (mSuppliedDialogInfo != null)
- ? mSuppliedDialogInfo.getNeutralButtonTextResId() : ID_NULL;
- if (buttonTextId != ID_NULL && mSuspendingAppResources != null) {
- try {
- return mSuspendingAppResources.getString(buttonTextId);
- } catch (Resources.NotFoundException nfe) {
- Slog.e(TAG, "Could not resolve string resource id " + buttonTextId);
+ if (mSuppliedDialogInfo != null) {
+ final int buttonTextId = mSuppliedDialogInfo.getNeutralButtonTextResId();
+ final String buttonText = mSuppliedDialogInfo.getNeutralButtonText();
+ if (buttonTextId != ID_NULL && mSuspendingAppResources != null) {
+ try {
+ return mSuspendingAppResources.getString(buttonTextId);
+ } catch (Resources.NotFoundException nfe) {
+ Slog.e(TAG, "Could not resolve string resource id " + buttonTextId);
+ }
+ } else if (buttonText != null) {
+ return buttonText;
}
}
return getString(defaultButtonTextId);
diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
index 1c222a7..9a02b7b 100644
--- a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
@@ -40,8 +40,7 @@
overrides = new HashMap<>();
for (int i = 0; i < keyCount; i++) {
long key = in.readLong();
- PackageOverride override = in.readParcelable(PackageOverride.class.getClassLoader());
- overrides.put(key, override);
+ overrides.put(key, PackageOverride.createFromParcel(in));
}
}
@@ -55,7 +54,7 @@
dest.writeInt(overrides.size());
for (Long key : overrides.keySet()) {
dest.writeLong(key);
- dest.writeParcelable(overrides.get(key), 0);
+ overrides.get(key).writeToParcel(dest);
}
}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 249c134..afcd0b0 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -151,15 +151,23 @@
void setOverrides(in CompatibilityChangeConfig overrides, in String packageName);
/**
- * Adds overrides to compatibility changes.
+ * Adds overrides to compatibility changes on release builds.
*
- * <p>Kills the app to allow the changes to take effect.
+ * <p>The caller to this API needs to hold
+ * {@code android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} and all change ids
+ * in {@code overrides} need to annotated with {@link android.compat.annotation.Overridable}.
+ *
+ * A release build in this definition means that {@link android.os.Build#IS_DEBUGGABLE} needs to
+ * be {@code false}.
+ *
+ * <p>Note that this does not kill the app, and therefore overrides read from the app process
+ * will not be updated. Overrides read from the system process do take effect.
*
* @param overrides parcelable containing the compat change overrides to be applied
* @param packageName the package name of the app whose changes will be overridden
* @throws SecurityException if overriding changes is not permitted
*/
- void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName);
+ void setOverridesOnReleaseBuilds(in CompatibilityOverrideConfig overrides, in String packageName);
/**
* Adds overrides to compatibility changes.
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index fae5862..6776c27 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -20,6 +20,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -28,6 +29,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
+import android.view.Display;
import java.util.LinkedList;
import java.util.Queue;
@@ -52,6 +54,7 @@
// This value is approximately 1/3 of the smallest possible brightness value.
public static final float EPSILON = 0.001f;
+ private DisplayManager mDisplayManager;
private final Context mContext;
private final Queue<Object> mWriteHistory = new LinkedList<>();
@@ -87,11 +90,15 @@
* value, if float is invalid. If both are invalid, use default float value from config.
*/
public void startSynchronizing() {
+ if (mDisplayManager == null) {
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ }
+
final BrightnessSyncObserver brightnessSyncObserver;
brightnessSyncObserver = new BrightnessSyncObserver(mHandler);
brightnessSyncObserver.startObserving();
- final float currentFloatBrightness = getScreenBrightnessFloat(mContext);
+ final float currentFloatBrightness = getScreenBrightnessFloat();
final int currentIntBrightness = getScreenBrightnessInt(mContext);
if (!Float.isNaN(currentFloatBrightness)) {
@@ -101,9 +108,7 @@
} else {
final float defaultBrightness = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat);
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, defaultBrightness,
- UserHandle.USER_CURRENT);
+ mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, defaultBrightness);
}
}
@@ -135,7 +140,7 @@
/**
* Translates specified value from the float brightness system to the int brightness system,
* given the min/max of each range. Accounts for special values such as OFF and invalid values.
- * Value returned as a float privimite (to preserve precision), but is a value within the
+ * Value returned as a float primitive (to preserve precision), but is a value within the
* int-system range.
*/
public static float brightnessFloatToIntRange(float brightnessFloat) {
@@ -152,10 +157,8 @@
}
}
- private static float getScreenBrightnessFloat(Context context) {
- return Settings.System.getFloatForUser(context.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT,
- UserHandle.USER_CURRENT);
+ private float getScreenBrightnessFloat() {
+ return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY);
}
private static int getScreenBrightnessInt(Context context) {
@@ -184,9 +187,7 @@
float newBrightnessFloat = brightnessIntToFloat(value);
mWriteHistory.offer(newBrightnessFloat);
mPreferredSettingValue = newBrightnessFloat;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, newBrightnessFloat,
- UserHandle.USER_CURRENT);
+ mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat);
}
}
@@ -255,7 +256,7 @@
mHandler.removeMessages(MSG_UPDATE_FLOAT);
mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
} else if (BRIGHTNESS_FLOAT_URI.equals(uri)) {
- float currentFloat = getScreenBrightnessFloat(mContext);
+ float currentFloat = getScreenBrightnessFloat();
int toSend = Float.floatToIntBits(currentFloat);
mHandler.removeMessages(MSG_UPDATE_INT);
mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ec0a8d8..b0920b0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -170,7 +170,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 195;
+ static final int VERSION = 196;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -1014,6 +1014,9 @@
@Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null;
/** Cpu Power calculator for attributing measured cpu charge consumption to uids */
@Nullable CpuPowerCalculator mCpuPowerCalculator = null;
+ /** Mobile Radio Power calculator for attributing measured radio charge consumption to uids */
+ @Nullable
+ MobileRadioPowerCalculator mMobileRadioPowerCalculator = null;
/** Wifi Power calculator for attributing measured wifi charge consumption to uids */
@Nullable WifiPowerCalculator mWifiPowerCalculator = null;
@@ -6983,6 +6986,16 @@
}
@Override
+ public long getGnssMeasuredBatteryConsumptionUC() {
+ return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
+ }
+
+ @Override
+ public long getMobileRadioMeasuredBatteryConsumptionUC() {
+ return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO);
+ }
+
+ @Override
public long getScreenOnMeasuredBatteryConsumptionUC() {
return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
}
@@ -7842,6 +7855,16 @@
}
@Override
+ public long getGnssMeasuredBatteryConsumptionUC() {
+ return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
+ }
+
+ @Override
+ public long getMobileRadioMeasuredBatteryConsumptionUC() {
+ return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO);
+ }
+
+ @Override
public long getScreenOnMeasuredBatteryConsumptionUC() {
return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
}
@@ -7880,6 +7903,27 @@
return (topTimeUs < fgTimeUs) ? topTimeUs : fgTimeUs;
}
+
+ /**
+ * Gets the uid's time spent using the GNSS since last marked. Also sets the mark time for
+ * the GNSS timer.
+ */
+ private long markGnssTimeUs(long elapsedRealtimeMs) {
+ final Sensor sensor = mSensorStats.get(Sensor.GPS);
+ if (sensor == null) {
+ return 0;
+ }
+
+ final StopwatchTimer timer = sensor.mTimer;
+ if (timer == null) {
+ return 0;
+ }
+
+ final long gnssTimeUs = timer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000);
+ timer.setMark(elapsedRealtimeMs);
+ return gnssTimeUs;
+ }
+
public StopwatchTimer createAudioTurnedOnTimerLocked() {
if (mAudioTurnedOnTimer == null) {
mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, AUDIO_TURNED_ON,
@@ -11823,7 +11867,7 @@
* Distribute Cell radio energy info and network traffic to apps.
*/
public void noteModemControllerActivity(@Nullable final ModemActivityInfo activityInfo,
- long elapsedRealtimeMs, long uptimeMs) {
+ final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) {
if (DEBUG_ENERGY) {
Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
}
@@ -11854,6 +11898,16 @@
return;
}
+ final SparseDoubleArray uidEstimatedConsumptionMah;
+ if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null
+ && mGlobalMeasuredEnergyStats != null) {
+ mGlobalMeasuredEnergyStats.updateStandardBucket(
+ MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC);
+ uidEstimatedConsumptionMah = new SparseDoubleArray();
+ } else {
+ uidEstimatedConsumptionMah = null;
+ }
+
if (deltaInfo != null) {
mHasModemReporting = true;
mModemActivity.getIdleTimeCounter().addCountLocked(
@@ -11898,7 +11952,7 @@
mTmpRailStats.resetCellularTotalEnergyUsed();
}
}
- long radioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
+ long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000);
mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
@@ -11958,12 +12012,21 @@
// Distribute total radio active time in to this app.
final long appPackets = entry.rxPackets + entry.txPackets;
- final long appRadioTimeUs = (radioTimeUs * appPackets) / totalPackets;
+ final long appRadioTimeUs =
+ (totalAppRadioTimeUs * appPackets) / totalPackets;
u.noteMobileRadioActiveTimeLocked(appRadioTimeUs);
+ // Distribute measured mobile radio charge consumption based on app radio
+ // active time
+ if (uidEstimatedConsumptionMah != null) {
+ uidEstimatedConsumptionMah.add(u.getUid(),
+ mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
+ appRadioTimeUs / 1000));
+ }
+
// Remove this app from the totals, so that we don't lose any time
// due to rounding.
- radioTimeUs -= appRadioTimeUs;
+ totalAppRadioTimeUs -= appRadioTimeUs;
totalPackets -= appPackets;
if (deltaInfo != null) {
@@ -11988,12 +12051,51 @@
}
}
- if (radioTimeUs > 0) {
+ if (totalAppRadioTimeUs > 0) {
// Whoops, there is some radio time we can't blame on an app!
- mMobileRadioActiveUnknownTime.addCountLocked(radioTimeUs);
+ mMobileRadioActiveUnknownTime.addCountLocked(totalAppRadioTimeUs);
mMobileRadioActiveUnknownCount.addCountLocked(1);
}
+
+ // Update the MeasuredEnergyStats information.
+ if (uidEstimatedConsumptionMah != null) {
+ double totalEstimatedConsumptionMah = 0.0;
+
+ // Estimate total active radio power consumption since last mark.
+ final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
+ totalEstimatedConsumptionMah +=
+ mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
+ totalRadioTimeMs);
+
+ // Estimate idle power consumption at each signal strength level
+ final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length;
+ for (int strengthLevel = 0; strengthLevel < numSignalStrengthLevels;
+ strengthLevel++) {
+ final long strengthLevelDurationMs =
+ mPhoneSignalStrengthsTimer[strengthLevel].getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ mPhoneSignalStrengthsTimer[strengthLevel].setMark(elapsedRealtimeMs);
+
+ totalEstimatedConsumptionMah +=
+ mMobileRadioPowerCalculator.calcIdlePowerAtSignalStrengthMah(
+ strengthLevelDurationMs, strengthLevel);
+ }
+
+ // Estimate total active radio power consumption since last mark.
+ final long scanTimeMs = mPhoneSignalScanningTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ mPhoneSignalScanningTimer.setMark(elapsedRealtimeMs);
+ totalEstimatedConsumptionMah +=
+ mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
+
+ distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
+ consumedChargeUC, uidEstimatedConsumptionMah,
+ totalEstimatedConsumptionMah);
+ }
+
mNetworkStatsPool.release(delta);
delta = null;
}
@@ -12453,7 +12555,7 @@
// 'double counted' and will simply exceed the realtime that elapsed.
// If multidisplay becomes a reality, this is probably more reasonable than pooling.
- // On the first pass, collect total time since mark so that we can normalize power.
+ // Collect total time since mark so that we can normalize power.
final SparseDoubleArray fgTimeUsArray = new SparseDoubleArray();
final long elapsedRealtimeUs = elapsedRealtimeMs * 1000;
// TODO(b/175726779): Update and optimize the algorithm (e.g. avoid iterating over ALL uids)
@@ -12468,6 +12570,50 @@
}
/**
+ * Accumulate GNSS charge consumption and distribute it to the correct state and the apps.
+ *
+ * @param chargeUC amount of charge (microcoulombs) used by GNSS since this was last called.
+ */
+ @GuardedBy("this")
+ public void updateGnssMeasuredEnergyStatsLocked(long chargeUC, long elapsedRealtimeMs) {
+ if (DEBUG_ENERGY) Slog.d(TAG, "Updating gnss stats: " + chargeUC);
+ if (mGlobalMeasuredEnergyStats == null) {
+ return;
+ }
+
+ if (!mOnBatteryInternal || chargeUC <= 0) {
+ // There's nothing further to update.
+ return;
+ }
+ if (mIgnoreNextExternalStats) {
+ // Although under ordinary resets we won't get here, and typically a new sync will
+ // happen right after the reset, strictly speaking we need to set all mark times to now.
+ final int uidStatsSize = mUidStats.size();
+ for (int i = 0; i < uidStatsSize; i++) {
+ final Uid uid = mUidStats.valueAt(i);
+ uid.markGnssTimeUs(elapsedRealtimeMs);
+ }
+ return;
+ }
+
+ mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_GNSS,
+ chargeUC);
+
+ // Collect the per uid time since mark so that we can normalize power.
+ final SparseDoubleArray gnssTimeUsArray = new SparseDoubleArray();
+ // TODO(b/175726779): Update and optimize the algorithm (e.g. avoid iterating over ALL uids)
+ final int uidStatsSize = mUidStats.size();
+ for (int i = 0; i < uidStatsSize; i++) {
+ final Uid uid = mUidStats.valueAt(i);
+ final long gnssTimeUs = uid.markGnssTimeUs(elapsedRealtimeMs);
+ if (gnssTimeUs == 0) continue;
+ gnssTimeUsArray.put(uid.getUid(), (double) gnssTimeUs);
+ }
+ distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_GNSS, chargeUC,
+ gnssTimeUsArray, 0);
+ }
+
+ /**
* Accumulate Custom power bucket charge, globally and for each app.
*
* @param totalChargeUC charge (microcoulombs) used for this bucket since this was last called.
@@ -14394,6 +14540,9 @@
if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU]) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
+ if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO]) {
+ mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile);
+ }
if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_WIFI]) {
mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
}
@@ -16525,6 +16674,8 @@
// Pull the clock time. This may update the time and make a new history entry
// if we had originally pulled a time before the RTC was set.
getStartClockTime();
+
+ updateSystemServiceCallStats();
}
public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 4f99c94..f8ae0c1 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -21,9 +21,7 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.Bundle;
import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -88,29 +86,31 @@
}
/**
+ * Returns true if the last update was too long ago for the tolerances specified
+ * by the supplied queries.
+ */
+ public boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries,
+ long lastUpdateTimeStampMs) {
+ long allowableStatsAge = Long.MAX_VALUE;
+ for (int i = queries.size() - 1; i >= 0; i--) {
+ BatteryUsageStatsQuery query = queries.get(i);
+ allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge());
+ }
+
+ return mStats.mClocks.elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge;
+ }
+
+ /**
* Returns snapshots of battery attribution data, one per supplied query.
*/
public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
-
- // TODO(b/174186345): instead of BatteryStatsHelper, use PowerCalculators directly.
- final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext,
- false /* collectBatteryBroadcast */);
- batteryStatsHelper.create((Bundle) null);
- final List<UserHandle> users = new ArrayList<>();
- for (int i = 0; i < queries.size(); i++) {
- BatteryUsageStatsQuery query = queries.get(i);
- for (int userId : query.getUserIds()) {
- UserHandle userHandle = UserHandle.of(userId);
- if (!users.contains(userHandle)) {
- users.add(userHandle);
- }
- }
- }
- batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users);
-
ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size());
- for (int i = 0; i < queries.size(); i++) {
- results.add(getBatteryUsageStats(queries.get(i)));
+ synchronized (mStats) {
+ mStats.prepareForDumpLocked();
+
+ for (int i = 0; i < queries.size(); i++) {
+ results.add(getBatteryUsageStats(queries.get(i)));
+ }
}
return results;
}
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index df25cda..97c4fd8 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -61,7 +61,17 @@
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
double averageGnssPowerMa) {
final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
- double powerMah = computePower(durationMs, averageGnssPowerMa);
+
+ final long measuredChargeUC = u.getGnssMeasuredBatteryConsumptionUC();
+ final boolean isMeasuredPowerAvailable = !query.shouldForceUsePowerProfileModel()
+ && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE;
+
+ final double powerMah;
+ if (isMeasuredPowerAvailable) {
+ powerMah = uCtoMah(measuredChargeUC);
+ } else {
+ powerMah = computePower(durationMs, averageGnssPowerMa);
+ }
app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah);
}
@@ -73,15 +83,25 @@
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper app = sippers.get(i);
if (app.drainType == BatterySipper.DrainType.APP) {
- calculateApp(app, app.uidObj, rawRealtimeUs, statsType, averageGnssPowerMa);
+ calculateApp(app, app.uidObj, rawRealtimeUs, statsType, averageGnssPowerMa, false);
}
}
}
protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
- int statsType, double averageGnssPowerMa) {
+ int statsType, double averageGnssPowerMa, boolean shouldForceUsePowerProfileModel) {
final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
- double powerMah = computePower(durationMs, averageGnssPowerMa);
+
+ final long measuredChargeUC = u.getGnssMeasuredBatteryConsumptionUC();
+ final boolean isMeasuredPowerAvailable = shouldForceUsePowerProfileModel
+ && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE;
+
+ final double powerMah;
+ if (isMeasuredPowerAvailable) {
+ powerMah = uCtoMah(measuredChargeUC);
+ } else {
+ powerMah = computePower(durationMs, averageGnssPowerMa);
+ }
app.gpsTimeMs = durationMs;
app.gpsPowerMah = powerMah;
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index 22001d4..498e1f2 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -96,10 +96,12 @@
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
final BatteryStats.Uid uid = app.getBatteryStatsUid();
- calculateApp(app, uid, powerPerPacketMah, total);
+ calculateApp(app, uid, powerPerPacketMah, total,
+ query.shouldForceUsePowerProfileModel());
}
- calculateRemaining(total, batteryStats, rawRealtimeUs);
+ calculateRemaining(total, batteryStats, rawRealtimeUs,
+ query.shouldForceUsePowerProfileModel());
if (total.powerMah != 0) {
builder.getOrCreateSystemBatteryConsumerBuilder(
@@ -111,11 +113,13 @@
}
private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
- double powerPerPacketMah, PowerAndDuration total) {
+ double powerPerPacketMah, PowerAndDuration total,
+ boolean shouldForceUsePowerProfileModel) {
final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
total.totalAppDurationMs += radioActiveDurationMs;
- final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs);
+ final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs,
+ shouldForceUsePowerProfileModel);
app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MOBILE_RADIO,
radioActiveDurationMs)
@@ -132,12 +136,12 @@
final BatterySipper app = sippers.get(i);
if (app.drainType == BatterySipper.DrainType.APP) {
final BatteryStats.Uid u = app.uidObj;
- calculateApp(app, u, statsType, mobilePowerPerPacket, total);
+ calculateApp(app, u, statsType, mobilePowerPerPacket, total, false);
}
}
BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
- calculateRemaining(total, batteryStats, rawRealtimeUs);
+ calculateRemaining(total, batteryStats, rawRealtimeUs, false);
if (total.powerMah != 0) {
if (total.signalDurationMs != 0) {
radio.noCoveragePercent =
@@ -154,9 +158,12 @@
}
private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
- double powerPerPacketMah, PowerAndDuration total) {
+ double powerPerPacketMah, PowerAndDuration total,
+ boolean shouldForceUsePowerProfileModel) {
app.mobileActive = calculateDuration(u, statsType);
- app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive);
+
+ app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive,
+ shouldForceUsePowerProfileModel);
total.totalAppDurationMs += app.mobileActive;
// Add cost of mobile traffic.
@@ -183,11 +190,19 @@
}
private double calculatePower(BatteryStats.Uid u, double powerPerPacketMah,
- long radioActiveDurationMs) {
+ long radioActiveDurationMs, boolean shouldForceUsePowerProfileModel) {
+
+ final long measuredChargeUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
+ final boolean isMeasuredPowerAvailable = !shouldForceUsePowerProfileModel
+ && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE;
+ if (isMeasuredPowerAvailable) {
+ return uCtoMah(measuredChargeUC);
+ }
+
if (radioActiveDurationMs > 0) {
// We are tracking when the radio is up, so can use the active time to
// determine power use.
- return mActivePowerEstimator.calculatePower(radioActiveDurationMs);
+ return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
} else {
// We are not tracking when the radio is up, so must approximate power use
// based on the number of packets.
@@ -202,18 +217,29 @@
}
private void calculateRemaining(MobileRadioPowerCalculator.PowerAndDuration total,
- BatteryStats batteryStats, long rawRealtimeUs) {
+ BatteryStats batteryStats, long rawRealtimeUs,
+ boolean shouldForceUsePowerProfileModel) {
long signalTimeMs = 0;
double powerMah = 0;
+
+ final long measuredChargeUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
+ final boolean isMeasuredPowerAvailable = !shouldForceUsePowerProfileModel
+ && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE;
+ if (isMeasuredPowerAvailable) {
+ powerMah = uCtoMah(measuredChargeUC);
+ }
+
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
- final double p = mIdlePowerEstimators[i].calculatePower(strengthTimeMs);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
- + formatCharge(p));
+ if (!isMeasuredPowerAvailable) {
+ final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+ + formatCharge(p));
+ }
+ powerMah += p;
}
- powerMah += p;
signalTimeMs += strengthTimeMs;
if (i == 0) {
total.noCoverageDurationMs = strengthTimeMs;
@@ -222,16 +248,21 @@
final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
- final double p = mScanPowerEstimator.calculatePower(scanningTimeMs);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(p));
- }
- powerMah += p;
long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
- if (remainingActiveTimeMs > 0) {
- powerMah += mActivePowerEstimator.calculatePower(remainingActiveTimeMs);
+
+ if (!isMeasuredPowerAvailable) {
+ final double p = calcScanTimePowerMah(scanningTimeMs);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(
+ p));
+ }
+ powerMah += p;
+
+ if (remainingActiveTimeMs > 0) {
+ powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
+ }
}
total.durationMs = radioActiveTimeMs;
total.powerMah = powerMah;
@@ -239,12 +270,35 @@
}
/**
+ * Calculates active radio power consumption (in milliamp-hours) from active radio duration.
+ */
+ public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) {
+ return mActivePowerEstimator.calculatePower(radioActiveDurationMs);
+ }
+
+ /**
+ * Calculates idle radio power consumption (in milliamp-hours) for time spent at a cell signal
+ * strength level.
+ * see {@link CellSignalStrength#getNumSignalStrengthLevels()}
+ */
+ public double calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel) {
+ return mIdlePowerEstimators[strengthLevel].calculatePower(strengthTimeMs);
+ }
+
+ /**
+ * Calculates radio scan power consumption (in milliamp-hours) from scan time.
+ */
+ public double calcScanTimePowerMah(long scanningTimeMs) {
+ return mScanPowerEstimator.calculatePower(scanningTimeMs);
+ }
+
+ /**
* Return estimated power (in mAh) of sending or receiving a packet with the mobile radio.
*/
private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) {
final long radioDataUptimeMs =
stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
- final double mobilePower = mActivePowerEstimator.calculatePower(radioDataUptimeMs);
+ final double mobilePower = calcPowerFromRadioActiveDurationMah(radioDataUptimeMs);
final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 39bde74..611fe29 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -32,6 +32,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -2508,6 +2509,7 @@
if (mDecor.mForceWindowDrawsBarBackgrounds) {
params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
}
+ params.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
}
if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
decor.setSystemUiVisibility(
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index 845b3e5..00a0627 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -54,7 +54,9 @@
public static final int POWER_BUCKET_CPU = 3;
public static final int POWER_BUCKET_WIFI = 4;
public static final int POWER_BUCKET_BLUETOOTH = 5;
- public static final int NUMBER_STANDARD_POWER_BUCKETS = 6; // Buckets above this are custom.
+ public static final int POWER_BUCKET_GNSS = 6;
+ public static final int POWER_BUCKET_MOBILE_RADIO = 7;
+ public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom.
@IntDef(prefix = {"POWER_BUCKET_"}, value = {
POWER_BUCKET_UNKNOWN,
@@ -64,6 +66,8 @@
POWER_BUCKET_CPU,
POWER_BUCKET_WIFI,
POWER_BUCKET_BLUETOOTH,
+ POWER_BUCKET_GNSS,
+ POWER_BUCKET_MOBILE_RADIO,
})
@Retention(RetentionPolicy.SOURCE)
public @interface StandardPowerBucket {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f1fa5db..20d257e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -221,7 +221,6 @@
static_libs: [
"libasync_safe",
- "libconnectivityframeworkutils",
"libbinderthreadstateutils",
"libdmabufinfo",
"libgif",
@@ -238,7 +237,6 @@
"android.hardware.camera.device@3.2",
"media_permission-aidl-cpp",
"libandroidicu",
- "libandroid_net",
"libbpf_android",
"libnetdbpf",
"libnetdutils",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 94ac183..0c3f265 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -156,7 +156,6 @@
extern int register_android_service_DataLoaderService(JNIEnv* env);
extern int register_android_os_incremental_IncrementalManager(JNIEnv* env);
extern int register_android_net_LocalSocketImpl(JNIEnv* env);
-extern int register_android_net_NetworkUtils(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_Hyphenator(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
@@ -1548,7 +1547,6 @@
REG_JNI(register_android_os_Trace),
REG_JNI(register_android_os_UEventObserver),
REG_JNI(register_android_net_LocalSocketImpl),
- REG_JNI(register_android_net_NetworkUtils),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_android_os_SharedMemory),
REG_JNI(register_android_os_incremental_IncrementalManager),
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f54ffc5..419dc6e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1658,10 +1658,12 @@
jobject jJankData = env->NewObject(gJankDataClassInfo.clazz,
gJankDataClassInfo.ctor, jankData[i].frameVsyncId, jankData[i].jankType);
env->SetObjectArrayElement(jJankDataArray, i, jJankData);
+ env->DeleteLocalRef(jJankData);
}
env->CallVoidMethod(target,
gJankDataListenerClassInfo.onJankDataAvailable,
jJankDataArray);
+ env->DeleteLocalRef(jJankDataArray);
env->DeleteLocalRef(target);
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index dca6002..530cb44 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -123,6 +123,8 @@
optional SettingProto gesture_silence_alerts_enabled = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gesture_wake_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gesture_setup_complete = 9 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Assist assist = 7;
@@ -296,6 +298,7 @@
optional SettingProto subtype_history = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto selected_input_method_subtype = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto show_ime_with_hard_keyboard = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto default_voice_input_method = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional InputMethods input_methods = 26;
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 0d23946..acb7429 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -58,6 +58,9 @@
optional bool is_screen_bright = 1;
optional bool is_screen_dim = 2;
optional bool is_screen_dream = 3;
+ optional int64 last_user_activity_time_ms = 4;
+ optional int64 last_user_activity_time_no_change_lights_ms = 5;
+ optional int32 display_group_id = 6;
}
// A com.android.server.power.PowerManagerService.UidState object.
message UidStateProto {
@@ -109,7 +112,7 @@
// The time we decided to do next long check. (In milliseconds timestamp)
optional int64 notify_long_next_check_ms = 19;
// Summarizes the effect of the user activity timer.
- optional UserActivityProto user_activity = 20;
+ repeated UserActivityProto user_activity = 20;
// If true, instructs the display controller to wait for the proximity
// sensor to go negative before turning the screen on.
optional bool is_request_wait_for_negative_proximity = 21;
@@ -134,8 +137,8 @@
// Timestamp of the last time the device was put to sleep.
optional int64 last_sleep_time_ms = 30;
// Timestamp of the last call to user activity.
- optional int64 last_user_activity_time_ms = 31;
- optional int64 last_user_activity_time_no_change_lights_ms = 32;
+ optional int64 last_user_activity_time_ms = 31 [deprecated = true];
+ optional int64 last_user_activity_time_no_change_lights_ms = 32 [deprecated = true];
// Timestamp of last interactive power hint.
optional int64 last_interactive_power_hint_time_ms = 33;
// Timestamp of the last screen brightness boost.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1d462bc..90755b9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2689,6 +2689,11 @@
<permission android:name="android.permission.CREATE_USERS"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Allows an application to access data blobs across users.
+ This permission is not available to third party applications. -->
+ <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- @hide Allows an application to set the profile owners and the device owner.
This permission is not available to third party applications.-->
<permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
@@ -3998,6 +4003,15 @@
<permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
android:protectionLevel="signature" />
+ <!-- Allows an app to schedule a prioritized alarm that can be used to perform
+ background work even when the device is in doze.
+ <p>Not for use by third-party applications.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.SCHEDULE_PRIORITIZED_ALARM"
+ android:protectionLevel="signature|privileged"/>
+
<!-- Allows an app to use exact alarm scheduling APIs to perform timing
sensitive background work.
-->
@@ -4227,6 +4241,11 @@
<permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
android:protectionLevel="normal" />
+ <!-- Allows an application to create new companion device associations.
+ @hide -->
+ <permission android:name="android.permission.ASSOCIATE_COMPANION_DEVICES"
+ android:protectionLevel="internal|role" />
+
<!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
<p>Not for use by third-party applications.
@hide
@@ -5424,12 +5443,6 @@
<permission android:name="android.permission.WATCH_APPOPS"
android:protectionLevel="signature|privileged" />
- <!-- Allows an application to directly open the "Open by default" page inside a package's
- Details screen.
- @hide <p>Not for use by third-party applications. -->
- <permission android:name="android.permission.OPEN_APP_OPEN_BY_DEFAULT_SETTINGS"
- android:protectionLevel="signature" />
-
<!-- Allows hidden API checks to be disabled when starting a process.
@hide <p>Not for use by third-party applications. -->
<permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS"
@@ -5556,9 +5569,17 @@
<permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"
android:protectionLevel="signature|privileged" />
<!-- Allows an app to override compat change config.
+ This permission only allows to override config on debuggable builds or test-apks and is
+ therefore a less powerful version of OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"
android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+ <!-- @SystemApi Allows an app to override compat change config on release builds.
+ Only ChangeIds that are annotated as @Overridable can be overridden on release builds.
+ @hide -->
+ <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"
+ android:protectionLevel="signature|privileged" />
<!-- Allows input events to be monitored. Very dangerous! @hide -->
<permission android:name="android.permission.MONITOR_INPUT"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index dc4f52e..986bb82 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1595,6 +1595,20 @@
<enum name="always" value="1" />
</attr>
+ <!-- Enable hardware memory tagging (ARM MTE) in this process.
+ When enabled, heap memory bugs like use-after-free and buffer overlow
+ are detected and result in an immediate ("sync" mode) or delayed ("async"
+ mode) crash instead of a silent memory corruption. Sync mode, while slower,
+ provides enhanced bug reports including stack traces at the time of allocation
+ and deallocation of memory, similar to AddressSanitizer.
+
+ See the <a href="https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety">ARM announcement</a>
+ for more details.
+
+ <p>This attribute can be applied to a
+ {@link android.R.styleable#AndroidManifestProcess process} tag, or to an
+ {@link android.R.styleable#AndroidManifestApplication application} tag (to supply
+ a default setting for all application components). -->
<attr name="memtagMode">
<enum name="default" value="-1" />
<enum name="off" value="0" />
@@ -1898,7 +1912,9 @@
<attr name="memtagMode" />
- <attr name="nativeHeapZeroInit" format="boolean" />
+ <!-- If {@code true} enables automatic zero initialization of all native heap
+ allocations. -->
+ <attr name="nativeHeapZeroInitialized" format="boolean" />
<!-- @hide no longer used, kept to preserve padding -->
<attr name="allowAutoRevokePermissionsExemption" format="boolean" />
@@ -2486,7 +2502,7 @@
<attr name="process" />
<attr name="gwpAsanMode" />
<attr name="memtagMode" />
- <attr name="nativeHeapZeroInit" />
+ <attr name="nativeHeapZeroInitialized" />
</declare-styleable>
<!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8df3221..b6c22bb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3732,6 +3732,13 @@
-->
<string name="config_defaultWellbeingPackage" translatable="false"></string>
+ <!-- The package name for the companion provider app.
+ This package must be trusted, as it has the permissions to associate apps with devices
+ without a UI prompt.
+ Example: "com.google.android.gms"
+ -->
+ <string name="config_companionProviderPackage" translatable="false"></string>
+
<!-- The component name for the default system attention service.
This service must be trusted, as it can be activated without explicit consent of the user.
See android.attention.AttentionManagerService.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 16feb4f..bc49818 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3059,7 +3059,7 @@
<public name="fontProviderSystemFontFamily" />
<public name="hand_second" />
<public name="memtagMode" />
- <public name="nativeHeapZeroInit" />
+ <public name="nativeHeapZeroInitialized" />
<!-- @hide @SystemApi -->
<public name="hotwordDetectionService" />
<public name="previewLayout" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e2974bc..dd64750 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1656,6 +1656,14 @@
<string name="face_recalibrate_notification_title">Re-enroll your face</string>
<!-- Notification content shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
<string name="face_recalibrate_notification_content">To improve recognition, please re-enroll your face</string>
+ <!-- Title of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
+ <string name="face_setup_notification_title">Set up face unlock</string>
+ <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
+ <string name="face_setup_notification_content">Unlock your phone by looking at it</string>
+ <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
+ <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_setup_notification_content">Tap to add a fingerprint</string>
<!-- Message shown during face acquisition when the face cannot be recognized [CHAR LIMIT=50] -->
<string name="face_acquired_insufficient">Couldn\u2019t capture accurate face data. Try again.</string>
@@ -5654,15 +5662,20 @@
<!-- Button text. This button turns on a user's work profile so they can access their work apps and data. [CHAR LIMIT=NONE] -->
<string name="resolver_switch_on_work">Tap to turn on</string>
- <!-- Error message. This text lets the user know that their current work apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_work_apps_available_share">No work apps can support this content</string>
- <!-- Error message. This text lets the user know that their current work apps can't open this specific content. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_work_apps_available_resolve">No work apps can open this content</string>
+ <!-- Error message. This text lets the user know that their current work apps don't support the specific content. [CHAR LIMIT=NONE] -->
+ <string name="resolver_no_work_apps_available">No work apps</string>
- <!-- Error message. This text lets the user know that their current personal apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_personal_apps_available_share">No personal apps can support this content</string>
- <!-- Error message. This text lets the user know that their current personal apps can't open this specific content. [CHAR LIMIT=NONE] -->
- <string name="resolver_no_personal_apps_available_resolve">No personal apps can open this content</string>
+ <!-- Error message. This text lets the user know that their current personal apps don't support the specific content. [CHAR LIMIT=NONE] -->
+ <string name="resolver_no_personal_apps_available">No personal apps</string>
+
+ <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string>
+ <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string>
+ <!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_use_personal_browser">Use personal browser</string>
+ <!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_use_work_browser">Use work browser</string>
<!-- Icc depersonalization related strings -->
<!-- Label text for PIN entry widget on SIM Network Depersonalization panel [CHAR LIMIT=none] -->
@@ -5909,9 +5922,9 @@
<!-- Window magnification prompt related string. -->
<!-- Notification title to prompt the user that new magnification feature is available. [CHAR LIMIT=50] -->
- <string name="window_magnification_prompt_title">Magnify part of your screen</string>
+ <string name="window_magnification_prompt_title">New magnification settings</string>
<!-- Notification content to prompt the user that new magnification feature is available. [CHAR LIMIT=NONE] -->
- <string name="window_magnification_prompt_content">You can now magnify your full screen, a specific area, or switch between both options.</string>
+ <string name="window_magnification_prompt_content">You can now magnify part of your screen.</string>
<!-- Notification action to bring the user to magnification settings page. [CHAR LIMIT=50] -->
<string name="turn_on_magnification_settings_action">Turn on in Settings</string>
<!-- Notification action to dismiss. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 01374b1..b924ecd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3536,6 +3536,7 @@
<java-symbol type="string" name="notification_channel_do_not_disturb" />
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
+ <java-symbol type="string" name="config_companionProviderPackage" />
<java-symbol type="string" name="config_defaultAutofillService" />
<java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
@@ -4101,10 +4102,8 @@
<java-symbol type="string" name="resolver_cant_access_work_apps_explanation" />
<java-symbol type="string" name="resolver_cant_access_personal_apps_explanation" />
<java-symbol type="string" name="resolver_turn_on_work_apps" />
- <java-symbol type="string" name="resolver_no_work_apps_available_share" />
- <java-symbol type="string" name="resolver_no_work_apps_available_resolve" />
- <java-symbol type="string" name="resolver_no_personal_apps_available_share" />
- <java-symbol type="string" name="resolver_no_personal_apps_available_resolve" />
+ <java-symbol type="string" name="resolver_no_work_apps_available" />
+ <java-symbol type="string" name="resolver_no_personal_apps_available" />
<java-symbol type="string" name="resolver_switch_on_work" />
<java-symbol type="drawable" name="ic_work_apps_off" />
<java-symbol type="drawable" name="ic_sharing_disabled" />
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index 6d9e2ea5..56c685a 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
@@ -42,8 +42,8 @@
public void setUp() throws Exception {
// Remove all documents from any instances that may have been created in the tests.
Objects.requireNonNull(mAppSearch);
- AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder()
- .setDatabaseName("testDb").build();
+ AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder("testDb").build();
CompletableFuture<AppSearchResult<AppSearchSession>> future = new CompletableFuture<>();
mAppSearch.createSearchSession(searchContext, mExecutor, future::complete);
mSearchSession = future.get().getResultValue();
diff --git a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java
index ffe93bc..21eb44a 100644
--- a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java
@@ -45,7 +45,7 @@
final Set<String> categorySet = new ArraySet<>();
categorySet.add(category);
final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
- final ShortcutInfo shortcut = new AppSearchShortcutInfo.Builder(id)
+ final ShortcutInfo shortcut = new AppSearchShortcutInfo.Builder(/*packageName=*/"", id)
.setActivity(activity)
.setLongLabel(id)
.setIconResName(shortcutIconResName)
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index f3a6f9e..22c71b52 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -32,7 +32,9 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.ICancellationSignal;
+import android.platform.test.annotations.Presubmit;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -45,6 +47,8 @@
* Tests of {@link ScrollCaptureConnection}.
*/
@SuppressWarnings("UnnecessaryLocalVariable")
+@Presubmit
+@SmallTest
@RunWith(AndroidJUnit4.class)
public class ScrollCaptureConnectionTest {
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
index cc229e1..dc43204 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
@@ -32,8 +32,10 @@
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -47,8 +49,10 @@
import java.util.function.Consumer;
/**
- * Tests of {@link ScrollCaptureTargetSelector}.
+ * Tests of {@link ScrollCaptureSearchResults}.
*/
+@Presubmit
+@SmallTest
@RunWith(AndroidJUnit4.class)
public class ScrollCaptureSearchResultsTest {
diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
index 41cd4c5..2833ea3 100644
--- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
@@ -49,7 +49,6 @@
*/
@Presubmit
@SmallTest
-@FlakyTest(detail = "promote once confirmed flake-free")
@RunWith(MockitoJUnitRunner.class)
public class ViewGroupScrollCaptureTest {
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
index 67614bb..e6a25d0 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
@@ -236,12 +236,13 @@
@Test
public void testMergeEvent_typeViewTextChanged() {
final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_TEXT_CHANGED)
- .setText("test");
+ .setText("test", false);
final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_TEXT_CHANGED)
- .setText("empty");
+ .setText("empty", true);
event.mergeEvent(event2);
assertThat(event.getText()).isEqualTo(event2.getText());
+ assertThat(event.getTextHasComposingSpan()).isEqualTo(event2.getTextHasComposingSpan());
}
@Test
@@ -282,16 +283,18 @@
@Test
public void testMergeEvent_differentEventTypes() {
final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED)
- .setText("test").setAutofillId(new AutofillId(1));
+ .setText("test", false).setAutofillId(new AutofillId(1));
final ContentCaptureEvent event2 = new ContentCaptureEvent(17, TYPE_VIEW_TEXT_CHANGED)
- .setText("empty").setAutofillId(new AutofillId(2));
+ .setText("empty", true).setAutofillId(new AutofillId(2));
event.mergeEvent(event2);
assertThat(event.getText()).isEqualTo("test");
+ assertThat(event.getTextHasComposingSpan()).isFalse();
assertThat(event.getId()).isEqualTo(new AutofillId(1));
event2.mergeEvent(event);
assertThat(event2.getText()).isEqualTo("empty");
+ assertThat(event2.getTextHasComposingSpan()).isTrue();
assertThat(event2.getId()).isEqualTo(new AutofillId(2));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 80d47a9..1633d28 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1660,7 +1660,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_share))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -1711,7 +1711,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_share))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -2146,7 +2146,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_resolve))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 68287ca..97652a9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -700,7 +700,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_resolve))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -751,7 +751,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_no_work_apps_available_resolve))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 0f59143..d36f06a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -158,4 +158,22 @@
assertThat(item.time).isEqualTo(elapsedTimeMs);
}
+
+ @Test
+ public void shouldUpdateStats() {
+ Context context = InstrumentationRegistry.getContext();
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
+ mStatsRule.getBatteryStats());
+
+ final List<BatteryUsageStatsQuery> queries = List.of(
+ new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(),
+ new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build()
+ );
+
+ mStatsRule.setTime(10500, 0);
+ assertThat(provider.shouldUpdateStats(queries, 10000)).isFalse();
+
+ mStatsRule.setTime(11500, 0);
+ assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue();
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
index bd7f1e2..eed61cb 100644
--- a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
+import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.UidBatteryConsumer;
@@ -35,12 +36,14 @@
private static final double PRECISION = 0.00001;
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 222;
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
.setAveragePower(PowerProfile.POWER_GPS_ON, 360.0)
.setAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED,
- new double[] {720.0, 1440.0, 1800.0});
+ new double[] {720.0, 1440.0, 1800.0})
+ .initMeasuredEnergyStatsLocked(0);
@Test
public void testTimerBasedModel() {
@@ -51,7 +54,8 @@
GnssPowerCalculator calculator =
new GnssPowerCalculator(mStatsRule.getPowerProfile());
- mStatsRule.apply(calculator);
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS))
@@ -59,4 +63,35 @@
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(0.1);
}
+
+ @Test
+ public void testMeasuredEnergyBasedModel() {
+ BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
+ uidStats.noteStartGps(1000);
+ uidStats.noteStopGps(2000);
+
+ BatteryStatsImpl.Uid uidStats2 = mStatsRule.getUidStats(APP_UID2);
+ uidStats2.noteStartGps(3000);
+ uidStats2.noteStopGps(5000);
+
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+ stats.updateGnssMeasuredEnergyStatsLocked(30_000_000, 6000);
+
+ GnssPowerCalculator calculator =
+ new GnssPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS))
+ .isEqualTo(1000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isWithin(PRECISION).of(2.77777);
+
+ UidBatteryConsumer consumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(consumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS))
+ .isEqualTo(2000);
+ assertThat(consumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isWithin(PRECISION).of(5.55555);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 66a8379..ae59a54 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -24,6 +26,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
import android.os.BatteryConsumer;
+import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
@@ -54,7 +57,8 @@
.setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
.setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
.setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
- new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0});
+ new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0})
+ .initMeasuredEnergyStatsLocked(0);
@Test
public void testCounterBasedModel() {
@@ -88,7 +92,59 @@
ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
new int[] {100, 200, 300, 400, 500}, 600);
- stats.noteModemControllerActivity(mai, 10000, 10000);
+ stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000);
+
+ mStatsRule.setTime(12_000_000, 12_000_000);
+
+ MobileRadioPowerCalculator calculator =
+ new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
+
+ SystemBatteryConsumer consumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.44440);
+
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.8);
+ }
+
+ @Test
+ public void testMeasuredEnergyBasedModel() {
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // Scan for a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+ TelephonyManager.SIM_STATE_READY,
+ 2000, 2000);
+
+ // Found a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+ 5000, 5000);
+
+ // Note cell signal strength
+ SignalStrength signalStrength = mock(SignalStrength.class);
+ when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 8_000_000_000L, APP_UID, 8000, 8000);
+
+ // Note established network
+ stats.noteNetworkInterfaceForTransports("cellular",
+ new int[] { NetworkCapabilities.TRANSPORT_CELLULAR });
+
+ // Note application network activity
+ NetworkStats networkStats = new NetworkStats(10000, 1)
+ .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100);
+ mStatsRule.setNetworkStats(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[] {100, 200, 300, 400, 500}, 600);
+ stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000);
mStatsRule.setTime(12_000_000, 12_000_000);
@@ -99,11 +155,13 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
+
+ // 100000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 2.77777 mAh
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(1.44440);
+ .isWithin(PRECISION).of(2.77777);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(0.8);
+ .isWithin(PRECISION).of(1.53934);
}
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index cafda09..c48fd8b 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -34,9 +34,11 @@
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.fonts.SystemFonts;
+import android.icu.util.ULocale;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.os.Trace;
import android.provider.FontRequest;
import android.provider.FontsContract;
@@ -45,6 +47,7 @@
import android.text.FontConfig;
import android.util.ArrayMap;
import android.util.Base64;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
import android.util.SparseArray;
@@ -1375,13 +1378,35 @@
static {
// Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
- // TODO: add new attribute to fonts.xml to preload fonts in Zygote.
preloadFontFile("/system/fonts/Roboto-Regular.ttf");
+
+ String locale = SystemProperties.get("persist.sys.locale", "en-US");
+ String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript();
+
+ FontConfig config = SystemFonts.getSystemPreinstalledFontConfig();
+ for (int i = 0; i < config.getFontFamilies().size(); ++i) {
+ FontConfig.FontFamily family = config.getFontFamilies().get(i);
+ boolean loadFamily = false;
+ for (int j = 0; j < family.getLocaleList().size(); ++j) {
+ String fontScript = ULocale.addLikelySubtags(
+ ULocale.forLocale(family.getLocaleList().get(j))).getScript();
+ loadFamily = fontScript.equals(script);
+ if (loadFamily) {
+ break;
+ }
+ }
+ if (loadFamily) {
+ for (int j = 0; j < family.getFontList().size(); ++j) {
+ preloadFontFile(family.getFontList().get(j).getFile().getAbsolutePath());
+ }
+ }
+ }
}
private static void preloadFontFile(String filePath) {
File file = new File(filePath);
if (file.exists()) {
+ Log.i(TAG, "Preloading " + file.getAbsolutePath());
nativeWarmUpCache(filePath);
}
}
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 35b1c16..72cea0c 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -139,4 +139,18 @@
return SYSTEM_ERROR;
}
}
+
+ /**
+ * Informs Keystore 2.0 that an off body event was detected.
+ */
+ public static void onDeviceOffBody() {
+ if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return;
+ try {
+ getService().onDeviceOffBody();
+ } catch (Exception e) {
+ // TODO This fails open. This is not a regression with respect to keystore1 but it
+ // should get fixed.
+ Log.e(TAG, "Error while reporting device off body event.", e);
+ }
+ }
}
diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java
new file mode 100644
index 0000000..a1a7aa8
--- /dev/null
+++ b/keystore/java/android/security/GenerateRkpKey.java
@@ -0,0 +1,99 @@
+/*
+ * 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.security;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner
+ * app. There are two cases where Keystore should use this class.
+ *
+ * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the
+ * RemoteProvisioner app check if the state of the attestation key pool is getting low enough
+ * to warrant provisioning more attestation certificates early.
+ *
+ * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of
+ * attestation key pairs and cannot provide one for the given application. Keystore can then
+ * make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another
+ * attestation certificate chain provisioned.
+ *
+ * In most cases, the proper usage of (1) should preclude the need for (2).
+ *
+ * @hide
+ */
+public class GenerateRkpKey {
+
+ private IGenerateRkpKeyService mBinder;
+ private Context mContext;
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mBinder = IGenerateRkpKeyService.Stub.asInterface(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ mBinder = null;
+ }
+ };
+
+ /**
+ * Constructor which takes a Context object.
+ */
+ public GenerateRkpKey(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Fulfills the use case of (2) described in the class documentation. Blocks until the
+ * RemoteProvisioner application can get new attestation keys signed by the server.
+ */
+ public void notifyEmpty(int securityLevel) throws RemoteException {
+ Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
+ ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+ intent.setComponent(comp);
+ if (comp == null || !mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+ throw new RemoteException("Failed to bind to GenerateKeyService");
+ }
+ if (mBinder != null) {
+ mBinder.generateKey(securityLevel);
+ }
+ mContext.unbindService(mConnection);
+ }
+
+ /**
+ * FUlfills the use case of (1) described in the class documentation. Non blocking call.
+ */
+ public void notifyKeyGenerated(int securityLevel) throws RemoteException {
+ Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
+ ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+ intent.setComponent(comp);
+ if (comp == null || !mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+ throw new RemoteException("Failed to bind to GenerateKeyService");
+ }
+ if (mBinder != null) {
+ mBinder.notifyKeyGenerated(securityLevel);
+ }
+ mContext.unbindService(mConnection);
+ }
+}
diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/keystore/java/android/security/GenerateRkpKeyException.java
new file mode 100644
index 0000000..a2d65e4
--- /dev/null
+++ b/keystore/java/android/security/GenerateRkpKeyException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.security;
+
+/**
+ * Thrown on problems in attempting to attest to a key using a remotely provisioned key.
+ *
+ * @hide
+ */
+public class GenerateRkpKeyException extends Exception {
+
+ /**
+ * Constructs a new {@code GenerateRkpKeyException}.
+ */
+ public GenerateRkpKeyException() {
+ }
+}
diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl
new file mode 100644
index 0000000..5f1d669
--- /dev/null
+++ b/keystore/java/android/security/IGenerateRkpKeyService.aidl
@@ -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 android.security;
+
+/**
+ * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This
+ * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an
+ * attestation request. The framework can then synchronously call generateKey() to get more
+ * attestation keys generated and signed. Upon return, the caller can be certain an attestation key
+ * is available.
+ *
+ * @hide
+ */
+interface IGenerateRkpKeyService {
+ /**
+ * Ping the provisioner service to let it know an app generated a key. This may or may not have
+ * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check.
+ */
+ oneway void notifyKeyGenerated(in int securityLevel);
+ /** Ping the provisioner service to indicate there are no remaining attestation keys left. */
+ void generateKey(in int securityLevel);
+}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index a08f390..b05149e 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -1204,6 +1204,7 @@
* Notify keystore that the device went off-body.
*/
public void onDeviceOffBody() {
+ AndroidKeyStoreMaintenance.onDeviceOffBody();
try {
mBinder.onDeviceOffBody();
} catch (RemoteException e) {
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index 6ac3821..75e248e 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -18,8 +18,7 @@
import android.annotation.NonNull;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
-import android.os.Build;
+import android.compat.annotation.Disabled;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
@@ -86,7 +85,7 @@
* successfully conclude an operation.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ @Disabled // See b/180133780
static final long KEYSTORE_OPERATION_CREATION_MAY_FAIL = 169897160L;
// Never use mBinder directly, use KeyStore2.getService() instead or better yet
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index e401add..2d8901a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -24,6 +24,9 @@
import android.hardware.security.keymint.SecurityLevel;
import android.hardware.security.keymint.Tag;
import android.os.Build;
+import android.os.RemoteException;
+import android.security.GenerateRkpKey;
+import android.security.GenerateRkpKeyException;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
import android.security.KeyStore2;
@@ -520,6 +523,18 @@
@Override
public KeyPair generateKeyPair() {
+ try {
+ return generateKeyPairHelper();
+ } catch (GenerateRkpKeyException e) {
+ try {
+ return generateKeyPairHelper();
+ } catch (GenerateRkpKeyException f) {
+ throw new ProviderException("Failed to provision new attestation keys.");
+ }
+ }
+ }
+
+ private KeyPair generateKeyPairHelper() throws GenerateRkpKeyException {
if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized");
}
@@ -557,13 +572,30 @@
AndroidKeyStorePublicKey publicKey =
AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
-
+ GenerateRkpKey keyGen = new GenerateRkpKey(KeyStore.getApplicationContext());
+ try {
+ if (mSpec.getAttestationChallenge() != null) {
+ keyGen.notifyKeyGenerated(securityLevel);
+ }
+ } catch (RemoteException e) {
+ // This is not really an error state, and necessarily does not apply to non RKP
+ // systems or hybrid systems where RKP is not currently turned on.
+ Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.");
+ }
success = true;
return new KeyPair(publicKey, publicKey.getPrivateKey());
} catch (android.security.KeyStoreException e) {
switch(e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
+ case ResponseCode.OUT_OF_KEYS:
+ GenerateRkpKey keyGen = new GenerateRkpKey(KeyStore.getApplicationContext());
+ try {
+ keyGen.notifyEmpty(securityLevel);
+ } catch (RemoteException f) {
+ throw new ProviderException("Failed to talk to RemoteProvisioner", f);
+ }
+ throw new GenerateRkpKeyException();
default:
ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 5e2e559..5f8d795 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -1246,20 +1246,42 @@
* Returns true if the Location came from a mock provider.
*
* @return true if this Location came from a mock provider, false otherwise
+ * @deprecated Prefer {@link #isMock()} instead.
*/
+ @Deprecated
public boolean isFromMockProvider() {
- return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0;
+ return isMock();
}
/**
* Flag this Location as having come from a mock provider or not.
*
* @param isFromMockProvider true if this Location came from a mock provider, false otherwise
+ * @deprecated Prefer {@link #setMock(boolean)} instead.
* @hide
*/
+ @Deprecated
@SystemApi
public void setIsFromMockProvider(boolean isFromMockProvider) {
- if (isFromMockProvider) {
+ setMock(isFromMockProvider);
+ }
+
+ /**
+ * Returns true if this location is marked as a mock location. If this location comes from the
+ * Android framework, this indicates that the location was provided by a test location provider,
+ * and thus may not be related to the actual location of the device.
+ *
+ * @see LocationManager#addTestProvider
+ */
+ public boolean isMock() {
+ return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0;
+ }
+
+ /**
+ * Sets whether this location is marked as a mock location.
+ */
+ public void setMock(boolean mock) {
+ if (mock) {
mFieldsMask |= HAS_MOCK_PROVIDER_MASK;
} else {
mFieldsMask &= ~HAS_MOCK_PROVIDER_MASK;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9aeef07..343d04f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3221,6 +3221,23 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int NUM_SOUND_EFFECTS = 16;
+ /** @hide */
+ @IntDef(prefix = { "FX_" }, value = {
+ FX_KEY_CLICK,
+ FX_FOCUS_NAVIGATION_UP,
+ FX_FOCUS_NAVIGATION_DOWN,
+ FX_FOCUS_NAVIGATION_LEFT,
+ FX_FOCUS_NAVIGATION_RIGHT,
+ FX_KEYPRESS_STANDARD,
+ FX_KEYPRESS_SPACEBAR,
+ FX_KEYPRESS_DELETE,
+ FX_KEYPRESS_RETURN,
+ FX_KEYPRESS_INVALID,
+ FX_BACK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SystemSoundEffect {}
+
/**
* @hide Number of FX_FOCUS_NAVIGATION_REPEAT_* sound effects
*/
@@ -3296,22 +3313,11 @@
/**
* Plays a sound effect (Key clicks, lid open/close...)
- * @param effectType The type of sound effect. One of
- * {@link #FX_KEY_CLICK},
- * {@link #FX_FOCUS_NAVIGATION_UP},
- * {@link #FX_FOCUS_NAVIGATION_DOWN},
- * {@link #FX_FOCUS_NAVIGATION_LEFT},
- * {@link #FX_FOCUS_NAVIGATION_RIGHT},
- * {@link #FX_KEYPRESS_STANDARD},
- * {@link #FX_KEYPRESS_SPACEBAR},
- * {@link #FX_KEYPRESS_DELETE},
- * {@link #FX_KEYPRESS_RETURN},
- * {@link #FX_KEYPRESS_INVALID},
- * {@link #FX_BACK},
+ * @param effectType The type of sound effect.
* NOTE: This version uses the UI settings to determine
* whether sounds are heard or not.
*/
- public void playSoundEffect(int effectType) {
+ public void playSoundEffect(@SystemSoundEffect int effectType) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
@@ -3330,24 +3336,13 @@
/**
* Plays a sound effect (Key clicks, lid open/close...)
- * @param effectType The type of sound effect. One of
- * {@link #FX_KEY_CLICK},
- * {@link #FX_FOCUS_NAVIGATION_UP},
- * {@link #FX_FOCUS_NAVIGATION_DOWN},
- * {@link #FX_FOCUS_NAVIGATION_LEFT},
- * {@link #FX_FOCUS_NAVIGATION_RIGHT},
- * {@link #FX_KEYPRESS_STANDARD},
- * {@link #FX_KEYPRESS_SPACEBAR},
- * {@link #FX_KEYPRESS_DELETE},
- * {@link #FX_KEYPRESS_RETURN},
- * {@link #FX_KEYPRESS_INVALID},
- * {@link #FX_BACK},
+ * @param effectType The type of sound effect.
* @param userId The current user to pull sound settings from
* NOTE: This version uses the UI settings to determine
* whether sounds are heard or not.
* @hide
*/
- public void playSoundEffect(int effectType, int userId) {
+ public void playSoundEffect(@SystemSoundEffect int effectType, int userId) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
@@ -3366,25 +3361,14 @@
/**
* Plays a sound effect (Key clicks, lid open/close...)
- * @param effectType The type of sound effect. One of
- * {@link #FX_KEY_CLICK},
- * {@link #FX_FOCUS_NAVIGATION_UP},
- * {@link #FX_FOCUS_NAVIGATION_DOWN},
- * {@link #FX_FOCUS_NAVIGATION_LEFT},
- * {@link #FX_FOCUS_NAVIGATION_RIGHT},
- * {@link #FX_KEYPRESS_STANDARD},
- * {@link #FX_KEYPRESS_SPACEBAR},
- * {@link #FX_KEYPRESS_DELETE},
- * {@link #FX_KEYPRESS_RETURN},
- * {@link #FX_KEYPRESS_INVALID},
- * {@link #FX_BACK},
+ * @param effectType The type of sound effect.
* @param volume Sound effect volume.
* The volume value is a raw scalar so UI controls should be scaled logarithmically.
* If a volume of -1 is specified, the AudioManager.STREAM_MUSIC stream volume minus 3dB will be used.
* NOTE: This version is for applications that have their own
* settings panel for enabling and controlling volume.
*/
- public void playSoundEffect(int effectType, float volume) {
+ public void playSoundEffect(@SystemSoundEffect int effectType, float volume) {
if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
return;
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8db75d6..b51777c 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -45,6 +45,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -4690,6 +4691,128 @@
private native void native_enableOnFrameRenderedListener(boolean enable);
+ /**
+ * Returns a list of vendor parameter names.
+ * <p>
+ * This method can be called in any codec state except for released state.
+ *
+ * @return a list containing supported vendor parameters; an empty
+ * list if no vendor parameters are supported. The order of the
+ * parameters is arbitrary.
+ * @throws IllegalStateException if in the Released state.
+ */
+ @NonNull
+ public List<String> getSupportedVendorParameters() {
+ return native_getSupportedVendorParameters();
+ }
+
+ @NonNull
+ private native List<String> native_getSupportedVendorParameters();
+
+ /**
+ * Contains description of a parameter.
+ */
+ public static class ParameterDescriptor {
+ private ParameterDescriptor() {}
+
+ /**
+ * Returns the name of the parameter.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the type of the parameter.
+ * {@link MediaFormat#TYPE_NULL} is never returned.
+ */
+ @MediaFormat.Type
+ public int getType() {
+ return mType;
+ }
+
+ private String mName;
+ private @MediaFormat.Type int mType;
+ }
+
+ /**
+ * Describe a parameter with the name.
+ * <p>
+ * This method can be called in any codec state except for released state.
+ *
+ * @param name name of the parameter to describe, typically one from
+ * {@link #getSupportedVendorParameters}.
+ * @return {@link ParameterDescriptor} object that describes the parameter.
+ * {@code null} if unrecognized / not able to describe.
+ * @throws IllegalStateException if in the Released state.
+ */
+ @Nullable
+ public ParameterDescriptor getParameterDescriptor(@NonNull String name) {
+ return native_getParameterDescriptor(name);
+ }
+
+ @Nullable
+ private native ParameterDescriptor native_getParameterDescriptor(@NonNull String name);
+
+ /**
+ * Subscribe to vendor parameters, so that changes to these parameters generate
+ * output format change event.
+ * <p>
+ * Unrecognized parameter names or standard (non-vendor) parameter names will be ignored.
+ * {@link #reset} also resets the list of subscribed parameters.
+ * If a parameter in {@code names} is already subscribed, it will remain subscribed.
+ * <p>
+ * This method can be called in any codec state except for released state. When called in
+ * running state with newly subscribed parameters, it takes effect no later than the
+ * processing of the subsequently queued buffer. For the new parameters, the codec will generate
+ * output format change event.
+ * <p>
+ * Note that any vendor parameters set in a {@link #configure} or
+ * {@link #setParameters} call are automatically subscribed.
+ * <p>
+ * See also {@link #INFO_OUTPUT_FORMAT_CHANGED} or {@link Callback#onOutputFormatChanged}
+ * for output format change events.
+ *
+ * @param names names of the vendor parameters to subscribe. This may be an empty list,
+ * and in that case this method will not change the list of subscribed parameters.
+ * @throws IllegalStateException if in the Released state.
+ */
+ public void subscribeToVendorParameters(@NonNull List<String> names) {
+ native_subscribeToVendorParameters(names);
+ }
+
+ private native void native_subscribeToVendorParameters(@NonNull List<String> names);
+
+ /**
+ * Unsubscribe from vendor parameters, so that changes to these parameters
+ * no longer generate output format change event.
+ * <p>
+ * Unrecognized parameter names, standard (non-vendor) parameter names will be ignored.
+ * {@link #reset} also resets the list of subscribed parameters.
+ * If a parameter in {@code names} is already unsubscribed, it will remain unsubscribed.
+ * <p>
+ * This method can be called in any codec state except for released state. When called in
+ * running state with newly unsubscribed parameters, it takes effect no later than the
+ * processing of the subsequently queued buffer.
+ * <p>
+ * Note that any vendor parameters set in a {@link #configure} or
+ * {@link #setParameters} call are automatically subscribed, and with this method
+ * they can be unsubscribed.
+ * <p>
+ * See also {@link #INFO_OUTPUT_FORMAT_CHANGED} or {@link Callback#onOutputFormatChanged}
+ * for output format change events.
+ *
+ * @param names names of the vendor parameters to unsubscribe. This may be an empty list,
+ * and in that case this method will not change the list of subscribed parameters.
+ * @throws IllegalStateException if in the Released state.
+ */
+ public void unsubscribeFromVendorParameters(@NonNull List<String> names) {
+ native_unsubscribeFromVendorParameters(names);
+ }
+
+ private native void native_unsubscribeFromVendorParameters(@NonNull List<String> names);
+
private EventHandler getEventHandlerOn(
@Nullable Handler handler, @NonNull EventHandler lastHandler) {
if (handler == null) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 06d0eb0..a9e8c26 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -3434,11 +3434,14 @@
public static final int BITRATE_MODE_VBR = 1;
/** Constant bitrate mode */
public static final int BITRATE_MODE_CBR = 2;
+ /** Constant bitrate mode with frame drops */
+ public static final int BITRATE_MODE_CBR_FD = 3;
private static final Feature[] bitrates = new Feature[] {
new Feature("VBR", BITRATE_MODE_VBR, true),
new Feature("CBR", BITRATE_MODE_CBR, false),
- new Feature("CQ", BITRATE_MODE_CQ, false)
+ new Feature("CQ", BITRATE_MODE_CQ, false),
+ new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false)
};
private static int parseBitrateMode(String mode) {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8daa303..1f6855a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -194,6 +194,14 @@
/**
* Starts scanning remote routes.
+ * <p>
+ * Route discovery can happen even when the {@link #startScan()} is not called.
+ * This is because the scanning could be started before by other apps.
+ * Therefore, calling this method after calling {@link #stopScan()} does not necessarily mean
+ * that the routes found before are removed and added again.
+ * <p>
+ * Use {@link RouteCallback} to get the route related events.
+ * <p>
* Note that calling start/stopScan is applied to all system routers in the same process.
*
* @see #stopScan()
@@ -208,6 +216,15 @@
/**
* Stops scanning remote routes to reduce resource consumption.
+ * <p>
+ * Route discovery can be continued even after this method is called.
+ * This is because the scanning is only turned off when all the apps stop scanning.
+ * Therefore, calling this method does not necessarily mean the routes are removed.
+ * Also, for the same reason it does not mean that {@link RouteCallback#onRoutesAdded(List)}
+ * is not called afterwards.
+ * <p>
+ * Use {@link RouteCallback} to get the route related events.
+ * <p>
* Note that calling start/stopScan is applied to all system routers in the same process.
*
* @see #startScan()
@@ -299,24 +316,6 @@
}
/**
- * Registers a callback to receive route related events when they change.
- * <p>
- * If the specified callback is already registered, its registration will be updated for the
- * given {@link Executor executor}.
- * <p>
- * This will be no-op for non-system routers.
- * @hide
- */
- @SystemApi
- public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull RouteCallback routeCallback) {
- if (!isSystemRouter()) {
- return;
- }
- registerRouteCallback(executor, routeCallback, RouteDiscoveryPreference.EMPTY);
- }
-
- /**
* Registers a callback to discover routes and to receive events when they change.
* <p>
* If the specified callback is already registered, its registration will be updated for the
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 6fefbe1..758a813 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -148,6 +148,14 @@
/**
* Starts scanning remote routes.
+ * <p>
+ * Route discovery can happen even when the {@link #startScan()} is not called.
+ * This is because the scanning could be started before by other apps.
+ * Therefore, calling this method after calling {@link #stopScan()} does not necessarily mean
+ * that the routes found before are removed and added again.
+ * <p>
+ * Use {@link Callback} to get the route related events.
+ * <p>
* @see #stopScan()
*/
public void startScan() {
@@ -163,6 +171,15 @@
/**
* Stops scanning remote routes to reduce resource consumption.
+ * <p>
+ * Route discovery can be continued even after this method is called.
+ * This is because the scanning is only turned off when all the apps stop scanning.
+ * Therefore, calling this method does not necessarily mean the routes are removed.
+ * Also, for the same reason it does not mean that {@link Callback#onRoutesAdded(List)}
+ * is not called afterwards.
+ * <p>
+ * Use {@link Callback} to get the route related events.
+ *
* @see #startScan()
*/
public void stopScan() {
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 2f95247..37fee84 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -68,9 +69,10 @@
private final Bundle mExtras;
/**
- * An empty discovery preference
+ * An empty discovery preference.
* @hide
*/
+ @SystemApi
public static final RouteDiscoveryPreference EMPTY =
new Builder(Collections.emptyList(), false).build();
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index ded2e1b..f694482 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -53,6 +53,7 @@
#include <media/MediaCodecBuffer.h>
#include <media/hardware/VideoAPI.h>
+#include <media/stagefright/CodecBase.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -84,6 +85,16 @@
EVENT_FIRST_TUNNEL_FRAME_READY = 4,
};
+// From MediaFormat.java
+enum {
+ TYPE_NULL = 0,
+ TYPE_INTEGER = 1,
+ TYPE_LONG = 2,
+ TYPE_FLOAT = 3,
+ TYPE_STRING = 4,
+ TYPE_BYTE_BUFFER = 5,
+};
+
static struct CryptoErrorCodes {
jint cryptoErrorNoKey;
jint cryptoErrorKeyExpired;
@@ -140,6 +151,8 @@
} gByteBufferInfo;
static struct {
+ jclass clazz;
+ jmethodID ctorId;
jmethodID sizeId;
jmethodID getId;
jmethodID addId;
@@ -154,6 +167,13 @@
jfieldID lockId;
} gLinearBlockInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctorId;
+ jfieldID nameId;
+ jfieldID typeId;
+} gDescriptorInfo;
+
struct fields_t {
jmethodID postEventFromNativeID;
jmethodID lockAndGetContextID;
@@ -937,6 +957,74 @@
(void)mCodec->setParameters(msg);
}
+status_t JMediaCodec::querySupportedVendorParameters(JNIEnv *env, jobject *namesObj) {
+ std::vector<std::string> names;
+ status_t status = mCodec->querySupportedVendorParameters(&names);
+ if (status != OK) {
+ return status;
+ }
+ *namesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId);
+ for (const std::string &name : names) {
+ ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(name.c_str())};
+ (void)env->CallBooleanMethod(*namesObj, gArrayListInfo.addId, nameStr.get());
+ }
+ return OK;
+}
+
+status_t JMediaCodec::describeParameter(JNIEnv *env, jstring name, jobject *descObj) {
+ const char *tmp = env->GetStringUTFChars(name, nullptr);
+ CodecParameterDescriptor desc;
+ status_t status = mCodec->describeParameter(tmp, &desc);
+ env->ReleaseStringUTFChars(name, tmp);
+ if (status != OK) {
+ return status;
+ }
+ jint type = TYPE_NULL;
+ switch (desc.type) {
+ case AMessage::kTypeInt32: type = TYPE_INTEGER; break;
+ case AMessage::kTypeSize:
+ case AMessage::kTypeInt64: type = TYPE_LONG; break;
+ case AMessage::kTypeFloat: type = TYPE_FLOAT; break;
+ case AMessage::kTypeString: type = TYPE_STRING; break;
+ case AMessage::kTypeBuffer: type = TYPE_BYTE_BUFFER; break;
+ default: type = TYPE_NULL; break;
+ }
+ if (type == TYPE_NULL) {
+ return BAD_VALUE;
+ }
+ *descObj = env->NewObject(gDescriptorInfo.clazz, gDescriptorInfo.ctorId);
+ env->SetObjectField(*descObj, gDescriptorInfo.nameId, name);
+ env->SetIntField(*descObj, gDescriptorInfo.typeId, type);
+ return OK;
+}
+
+static void BuildVectorFromList(JNIEnv *env, jobject list, std::vector<std::string> *vec) {
+ ScopedLocalRef<jclass> listClazz{env, env->FindClass("java/util/List")};
+ ScopedLocalRef<jclass> iterClazz{env, env->FindClass("java/util/Iterator")};
+ jmethodID hasNextID = env->GetMethodID(iterClazz.get(), "hasNext", "()Z");
+ jmethodID nextID = env->GetMethodID(iterClazz.get(), "next", "()Ljava/lang/Object;");
+ jobject it = env->CallObjectMethod(
+ list, env->GetMethodID(listClazz.get(), "iterator", "()Ljava/util/Iterator;"));
+ while (env->CallBooleanMethod(it, hasNextID)) {
+ jstring name = (jstring)env->CallObjectMethod(it, nextID);
+ const char *tmp = env->GetStringUTFChars(name, nullptr);
+ vec->push_back(tmp);
+ env->ReleaseStringUTFChars(name, tmp);
+ }
+}
+
+status_t JMediaCodec::subscribeToVendorParameters(JNIEnv *env, jobject namesObj) {
+ std::vector<std::string> names;
+ BuildVectorFromList(env, namesObj, &names);
+ return mCodec->subscribeToVendorParameters(names);
+}
+
+status_t JMediaCodec::unsubscribeFromVendorParameters(JNIEnv *env, jobject namesObj) {
+ std::vector<std::string> names;
+ BuildVectorFromList(env, namesObj, &names);
+ return mCodec->unsubscribeFromVendorParameters(names);
+}
+
static jthrowable createCodecException(
JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
ScopedLocalRef<jclass> clazz(
@@ -2671,6 +2759,73 @@
codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId);
}
+static jobject android_media_MediaCodec_getSupportedVendorParameters(
+ JNIEnv *env, jobject thiz) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return NULL;
+ }
+
+ jobject ret = NULL;
+ status_t status = codec->querySupportedVendorParameters(env, &ret);
+ if (status != OK) {
+ throwExceptionAsNecessary(env, status);
+ }
+
+ return ret;
+}
+
+static jobject android_media_MediaCodec_getParameterDescriptor(
+ JNIEnv *env, jobject thiz, jstring name) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return NULL;
+ }
+
+ jobject ret = NULL;
+ status_t status = codec->describeParameter(env, name, &ret);
+ if (status != OK) {
+ ret = NULL;
+ }
+ return ret;
+}
+
+static void android_media_MediaCodec_subscribeToVendorParameters(
+ JNIEnv *env, jobject thiz, jobject names) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ status_t status = codec->subscribeToVendorParameters(env, names);
+ if (status != OK) {
+ throwExceptionAsNecessary(env, status);
+ }
+ return;
+}
+
+static void android_media_MediaCodec_unsubscribeFromVendorParameters(
+ JNIEnv *env, jobject thiz, jobject names) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ status_t status = codec->unsubscribeFromVendorParameters(env, names);
+ if (status != OK) {
+ throwExceptionAsNecessary(env, status);
+ }
+ return;
+}
+
static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
ScopedLocalRef<jclass> clazz(
env, env->FindClass("android/media/MediaCodec"));
@@ -2930,6 +3085,10 @@
clazz.reset(env->FindClass("java/util/ArrayList"));
CHECK(clazz.get() != NULL);
+ gArrayListInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ gArrayListInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gArrayListInfo.ctorId != NULL);
gArrayListInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I");
CHECK(gArrayListInfo.sizeId != NULL);
@@ -2960,6 +3119,19 @@
gLinearBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;");
CHECK(gLinearBlockInfo.lockId != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaCodec$ParameterDescriptor"));
+ CHECK(clazz.get() != NULL);
+ gDescriptorInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ gDescriptorInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gDescriptorInfo.ctorId != NULL);
+
+ gDescriptorInfo.nameId = env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;");
+ CHECK(gDescriptorInfo.nameId != NULL);
+
+ gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I");
+ CHECK(gDescriptorInfo.typeId != NULL);
}
static void android_media_MediaCodec_native_setup(
@@ -3289,6 +3461,21 @@
{ "native_setAudioPresentation", "(II)V",
(void *)android_media_MediaCodec_setAudioPresentation },
+ { "native_getSupportedVendorParameters", "()Ljava/util/List;",
+ (void *)android_media_MediaCodec_getSupportedVendorParameters },
+
+ { "native_getParameterDescriptor",
+ "(Ljava/lang/String;)Landroid/media/MediaCodec$ParameterDescriptor;",
+ (void *)android_media_MediaCodec_getParameterDescriptor },
+
+ { "native_subscribeToVendorParameters",
+ "(Ljava/util/List;)V",
+ (void *)android_media_MediaCodec_subscribeToVendorParameters},
+
+ { "native_unsubscribeFromVendorParameters",
+ "(Ljava/util/List;)V",
+ (void *)android_media_MediaCodec_unsubscribeFromVendorParameters},
+
{ "native_init", "()V", (void *)android_media_MediaCodec_native_init },
{ "native_setup", "(Ljava/lang/String;ZZ)V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 5fd6bfd..ee456c9 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -164,6 +164,14 @@
void selectAudioPresentation(const int32_t presentationId, const int32_t programId);
+ status_t querySupportedVendorParameters(JNIEnv *env, jobject *names);
+
+ status_t describeParameter(JNIEnv *env, jstring name, jobject *desc);
+
+ status_t subscribeToVendorParameters(JNIEnv *env, jobject names);
+
+ status_t unsubscribeFromVendorParameters(JNIEnv *env, jobject names);
+
bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; }
const sp<ICrypto> &getCrypto() { return mCrypto; }
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 80c68f2..74cecdd 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -35,7 +35,6 @@
":framework-javastream-protos",
],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
jarjar_rules: "jarjar-rules-proto.txt",
@@ -82,11 +81,13 @@
java_sdk_library {
name: "framework-connectivity",
- api_only: true,
+ sdk_version: "module_current",
+ min_sdk_version: "30",
defaults: ["framework-module-defaults"],
installable: true,
srcs: [
":framework-connectivity-sources",
+ ":net-utils-framework-common-srcs",
],
aidl: {
include_dirs: [
@@ -98,10 +99,33 @@
"frameworks/native/aidl/binder", // For PersistableBundle.aidl
],
},
+ impl_only_libs: [
+ // TODO (b/183097033) remove once module_current includes core_platform
+ "stable.core.platform.api.stubs",
+ "framework-tethering.stubs.module_lib",
+ "framework-wifi.stubs.module_lib",
+ "net-utils-device-common",
+ ],
libs: [
"unsupportedappusage",
],
+ static_libs: [
+ "framework-connectivity-protos",
+ ],
+ jarjar_rules: "jarjar-rules.txt",
permitted_packages: ["android.net"],
+ impl_library_visibility: [
+ "//packages/modules/Connectivity/Tethering/apex",
+ // In preparation for future move
+ "//packages/modules/Connectivity/apex",
+ "//packages/modules/Connectivity/service",
+ "//frameworks/base/packages/Connectivity/service",
+ "//frameworks/base",
+ "//packages/modules/Connectivity/Tethering/tests/unit",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
}
cc_defaults {
@@ -149,37 +173,6 @@
shared_libs: ["libandroid"],
stl: "libc++_static",
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
}
-
-java_library {
- name: "framework-connectivity.impl",
- sdk_version: "module_current",
- min_sdk_version: "30",
- srcs: [
- ":framework-connectivity-sources",
- ],
- aidl: {
- include_dirs: [
- "frameworks/base/core/java", // For framework parcelables
- "frameworks/native/aidl/binder", // For PersistableBundle.aidl
- ],
- },
- libs: [
- // TODO (b/183097033) remove once module_current includes core_current
- "stable.core.platform.api.stubs",
- "framework-tethering",
- "framework-wifi",
- "unsupportedappusage",
- ],
- static_libs: [
- "framework-connectivity-protos",
- "net-utils-device-common",
- ],
- jarjar_rules: "jarjar-rules.txt",
- apex_available: ["com.android.tethering"],
- installable: true,
- permitted_packages: ["android.net"],
-}
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 2bf807c4..0cc8c23 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -20,6 +20,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
+ method public static void setPrivateDnsMode(@NonNull android.content.Context, @NonNull String);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
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);
@@ -43,6 +44,52 @@
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
}
+ public class ConnectivitySettingsManager {
+ method public static void clearGlobalProxy(@NonNull android.content.Context);
+ method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context);
+ method public static int getCaptivePortalMode(@NonNull android.content.Context, int);
+ method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method @NonNull public static android.util.Range<java.lang.Integer> getDnsResolverSampleRanges(@NonNull android.content.Context);
+ method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int);
+ method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context);
+ method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+ method @Nullable public static String getMobileDataPreferredApps(@NonNull android.content.Context);
+ method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context);
+ method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context);
+ method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int);
+ method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context);
+ method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context);
+ method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+ method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String);
+ method public static void setCaptivePortalMode(@NonNull android.content.Context, int);
+ method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range<java.lang.Integer>);
+ method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int);
+ method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo);
+ method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+ method public static void setMobileDataPreferredApps(@NonNull android.content.Context, @Nullable String);
+ method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int);
+ method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String);
+ method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int);
+ method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull String);
+ method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String);
+ method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+ method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+ field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+ field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+ field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2
+ field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0
+ field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1
+ }
+
public final class NetworkAgentConfig implements android.os.Parcelable {
method @Nullable public String getSubscriberId();
method public boolean isBypassableVpn();
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 9dcc391..593698e 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -218,6 +218,8 @@
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
method public void onAutomaticReconnectDisabled();
method public void onBandwidthUpdateRequested();
+ method public void onNetworkCreated();
+ method public void onNetworkDisconnected();
method public void onNetworkUnwanted();
method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
method public void onQosCallbackUnregistered(int);
@@ -232,8 +234,8 @@
method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
method public final void sendNetworkScore(@IntRange(from=0, to=99) int);
method public final void sendQosCallbackError(int, int);
- method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes);
- method public final void sendQosSessionLost(int, int);
+ method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
+ method public final void sendQosSessionLost(int, int, int);
method public final void sendSocketKeepaliveEvent(int, int);
method @Deprecated public void setLegacySubtype(int, @NonNull String);
method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
@@ -385,6 +387,7 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR;
field public static final int TYPE_EPS_BEARER = 1; // 0x1
+ field public static final int TYPE_NR_BEARER = 2; // 0x2
}
public interface QosSessionAttributes {
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index a73d76e..a9f8b8d 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -3326,9 +3326,10 @@
* Register or update a network offer with ConnectivityService.
*
* ConnectivityService keeps track of offers made by the various providers and matches
- * them to networking requests made by apps or the system. The provider supplies a score
- * and the capabilities of the network it might be able to bring up ; these act as filters
- * used by ConnectivityService to only send those requests that can be fulfilled by the
+ * them to networking requests made by apps or the system. A callback identifies an offer
+ * uniquely, and later calls with the same callback update the offer. The provider supplies a
+ * score and the capabilities of the network it might be able to bring up ; these act as
+ * filters used by ConnectivityService to only send those requests that can be fulfilled by the
* provider.
*
* The provider is under no obligation to be able to bring up the network it offers at any
@@ -5426,4 +5427,23 @@
if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
return mode;
}
+
+ /**
+ * Set private DNS mode to settings.
+ *
+ * @param context The {@link Context} to set the private DNS mode.
+ * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void setPrivateDnsMode(@NonNull Context context,
+ @NonNull @PrivateDnsMode String mode) {
+ if (!(mode == PRIVATE_DNS_MODE_OFF
+ || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
+ || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
+ throw new IllegalArgumentException("Invalid private dns mode");
+ }
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, mode);
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
index bbd8393..9a00055 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
@@ -16,16 +16,38 @@
package android.net;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.net.ConnectivityManager.MultipathPreference;
+import android.net.ConnectivityManager.PrivateDnsMode;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Range;
+
+import com.android.net.module.util.ProxyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.util.List;
/**
* A manager class for connectivity module settings.
*
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public class ConnectivitySettingsManager {
private ConnectivitySettingsManager() {}
@@ -45,12 +67,16 @@
* Network activity refers to transmitting or receiving data on the network interfaces.
*
* Tracking is disabled if set to zero or negative value.
+ *
+ * @hide
*/
public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile";
/**
* Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE}
* but for Wifi network.
+ *
+ * @hide
*/
public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi";
@@ -58,12 +84,16 @@
/**
* Sample validity in seconds to configure for the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS =
"dns_resolver_sample_validity_seconds";
/**
* Success threshold in percent for use with the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT =
"dns_resolver_success_threshold_percent";
@@ -71,24 +101,35 @@
/**
* Minimum number of samples needed for statistics to be considered meaningful in the
* system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples";
/**
* Maximum number taken into account for statistics purposes in the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples";
+ private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
+ private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
+
/** Network switch notification settings */
/**
* The maximum number of notifications shown in 24 hours when switching networks.
+ *
+ * @hide
*/
public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT =
"network_switch_notification_daily_limit";
/**
* The minimum time in milliseconds between notifications when switching networks.
+ *
+ * @hide
*/
public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS =
"network_switch_notification_rate_limit_millis";
@@ -98,14 +139,18 @@
/**
* The URL used for HTTP captive portal detection upon a new connection.
* A 204 response code from the server is used for validation.
+ *
+ * @hide
*/
public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
/**
* What to do when connecting a network that presents a captive portal.
- * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+ * Must be one of the CAPTIVE_PORTAL_MODE_* constants below.
*
* The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+ *
+ * @hide
*/
public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
@@ -139,11 +184,15 @@
/**
* Host name for global http proxy. Set via ConnectivityManager.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
/**
* Integer host port for global http proxy. Set via ConnectivityManager.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
@@ -153,12 +202,16 @@
* Domains should be listed in a comma- separated list. Example of
* acceptable formats: ".domain1.com,my.domain2.com" Use
* ConnectivityManager to set/get.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST =
"global_http_proxy_exclusion_list";
/**
* The location PAC File for the proxy.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url";
@@ -171,11 +224,15 @@
* a specific provider. It may be used to store the provider name even when the
* mode changes so that temporarily disabling and re-enabling the specific
* provider mode does not necessitate retyping the provider hostname.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_MODE = "private_dns_mode";
/**
* The specific Private DNS provider name.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
@@ -185,6 +242,8 @@
* all of which require explicit user action to enable/configure. See also b/79719289.
*
* Value is a string, suitable for assignment to PRIVATE_DNS_MODE above.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
@@ -194,6 +253,8 @@
* The number of milliseconds to hold on to a PendingIntent based request. This delay gives
* the receivers of the PendingIntent an opportunity to make a new network request before
* the Network satisfying the request is potentially removed.
+ *
+ * @hide
*/
public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS =
"connectivity_release_pending_intent_delay_ms";
@@ -205,6 +266,8 @@
* See ConnectivityService for more info.
*
* (0 = disabled, 1 = enabled)
+ *
+ * @hide
*/
public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
@@ -217,6 +280,8 @@
* See ConnectivityService for more info.
*
* (0 = disabled, 1 = enabled)
+ *
+ * @hide
*/
public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested";
@@ -228,14 +293,637 @@
* 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
* null: Ask the user whether to switch away from bad wifi.
* 1: Avoid bad wifi.
+ *
+ * @hide
*/
public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
/**
+ * Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0;
+
+ /**
+ * Ask the user whether to switch away from bad wifi.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1;
+
+ /**
+ * Avoid bad wifi.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ NETWORK_AVOID_BAD_WIFI_IGNORE,
+ NETWORK_AVOID_BAD_WIFI_PROMPT,
+ NETWORK_AVOID_BAD_WIFI_AVOID,
+ })
+ public @interface NetworkAvoidBadWifi {}
+
+ /**
* User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be
* overridden by the system based on device or application state. If null, the value
* specified by config_networkMeteredMultipathPreference is used.
+ *
+ * @hide
*/
public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
"network_metered_multipath_preference";
+
+ /**
+ * A list of apps that should go on cellular networks in preference even when higher-priority
+ * networks are connected.
+ *
+ * @hide
+ */
+ public static final String MOBILE_DATA_PREFERRED_APPS = "mobile_data_preferred_apps";
+
+ /**
+ * Get mobile data activity timeout from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default timeout if no setting value.
+ * @return The {@link Duration} of timeout to track mobile data activity.
+ */
+ @NonNull
+ public static Duration getMobileDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration def) {
+ final int timeout = Settings.Global.getInt(
+ context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, (int) def.getSeconds());
+ return Duration.ofSeconds(timeout);
+ }
+
+ /**
+ * Set mobile data activity timeout to {@link Settings}.
+ * Tracking is disabled if set to zero or negative value.
+ *
+ * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
+ * ignored.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param timeout The mobile data activity timeout.
+ */
+ public static void setMobileDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration timeout) {
+ Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE,
+ (int) timeout.getSeconds());
+ }
+
+ /**
+ * Get wifi data activity timeout from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default timeout if no setting value.
+ * @return The {@link Duration} of timeout to track wifi data activity.
+ */
+ @NonNull
+ public static Duration getWifiDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration def) {
+ final int timeout = Settings.Global.getInt(
+ context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, (int) def.getSeconds());
+ return Duration.ofSeconds(timeout);
+ }
+
+ /**
+ * Set wifi data activity timeout to {@link Settings}.
+ * Tracking is disabled if set to zero or negative value.
+ *
+ * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
+ * ignored.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param timeout The wifi data activity timeout.
+ */
+ public static void setWifiDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration timeout) {
+ Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI,
+ (int) timeout.getSeconds());
+ }
+
+ /**
+ * Get dns resolver sample validity duration from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default duration if no setting value.
+ * @return The {@link Duration} of sample validity duration to configure for the system DNS
+ * resolver.
+ */
+ @NonNull
+ public static Duration getDnsResolverSampleValidityDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, (int) def.getSeconds());
+ return Duration.ofSeconds(duration);
+ }
+
+ /**
+ * Set dns resolver sample validity duration to {@link Settings}. The duration must be a
+ * positive number of seconds.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The sample validity duration.
+ */
+ public static void setDnsResolverSampleValidityDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.getSeconds();
+ if (time <= 0) {
+ throw new IllegalArgumentException("Invalid duration");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, time);
+ }
+
+ /**
+ * Get dns resolver success threshold percent from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return The success threshold in percent for use with the system DNS resolver.
+ */
+ public static int getDnsResolverSuccessThresholdPercent(@NonNull Context context, int def) {
+ return Settings.Global.getInt(
+ context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, def);
+ }
+
+ /**
+ * Set dns resolver success threshold percent to {@link Settings}. The threshold percent must
+ * be 0~100.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param percent The success threshold percent.
+ */
+ public static void setDnsResolverSuccessThresholdPercent(@NonNull Context context,
+ @IntRange(from = 0, to = 100) int percent) {
+ if (percent < 0 || percent > 100) {
+ throw new IllegalArgumentException("Percent must be 0~100");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, percent);
+ }
+
+ /**
+ * Get dns resolver samples range from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The {@link Range<Integer>} of samples needed for statistics to be considered
+ * meaningful in the system DNS resolver.
+ */
+ @NonNull
+ public static Range<Integer> getDnsResolverSampleRanges(@NonNull Context context) {
+ final int minSamples = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
+ final int maxSamples = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
+ return new Range<>(minSamples, maxSamples);
+ }
+
+ /**
+ * Set dns resolver samples range to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param range The samples range. The minimum number should be more than 0 and the maximum
+ * number should be less that 64.
+ */
+ public static void setDnsResolverSampleRanges(@NonNull Context context,
+ @NonNull Range<Integer> range) {
+ if (range.getLower() < 0 || range.getUpper() > 64) {
+ throw new IllegalArgumentException("Argument must be 0~64");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_MIN_SAMPLES, range.getLower());
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_MAX_SAMPLES, range.getUpper());
+ }
+
+ /**
+ * Get maximum count (from {@link Settings}) of switching network notifications shown in 24
+ * hours.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return The maximum count of notifications shown in 24 hours when switching networks.
+ */
+ public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
+ int def) {
+ return Settings.Global.getInt(
+ context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, def);
+ }
+
+ /**
+ * Set maximum count (to {@link Settings}) of switching network notifications shown in 24 hours.
+ * The count must be at least 0.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param count The maximum count of switching network notifications shown in 24 hours.
+ */
+ public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
+ @IntRange(from = 0) int count) {
+ if (count < 0) {
+ throw new IllegalArgumentException("Count must be 0~10.");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count);
+ }
+
+ /**
+ * Get minimum duration (from {@link Settings}) between each switching network notifications.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default time if no setting value.
+ * @return The minimum duration between notifications when switching networks.
+ */
+ @NonNull
+ public static Duration getNetworkSwitchNotificationRateDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Global.getInt(context.getContentResolver(),
+ NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, (int) def.toMillis());
+ return Duration.ofMillis(duration);
+ }
+
+ /**
+ * Set minimum duration (to {@link Settings}) between each switching network notifications.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The minimum duration between notifications when switching networks.
+ */
+ public static void setNetworkSwitchNotificationRateDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.toMillis();
+ if (time < 0) {
+ throw new IllegalArgumentException("Invalid duration.");
+ }
+ Settings.Global.putInt(context.getContentResolver(),
+ NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, time);
+ }
+
+ /**
+ * Get URL (from {@link Settings}) used for HTTP captive portal detection upon a new connection.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The URL used for HTTP captive portal detection upon a new connection.
+ */
+ @Nullable
+ public static String getCaptivePortalHttpUrl(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL);
+ }
+
+ /**
+ * Set URL (to {@link Settings}) used for HTTP captive portal detection upon a new connection.
+ * This URL should respond with a 204 response to a GET request to indicate no captive portal is
+ * present. And this URL must be HTTP as redirect responses are used to find captive portal
+ * sign-in pages. If the URL set to null or be incorrect, it will result in captive portal
+ * detection failed and lost the connection.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param url The URL used for HTTP captive portal detection upon a new connection.
+ */
+ public static void setCaptivePortalHttpUrl(@NonNull Context context, @Nullable String url) {
+ Settings.Global.putString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL, url);
+ }
+
+ /**
+ * Get mode (from {@link Settings}) when connecting a network that presents a captive portal.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default mode if no setting value.
+ * @return The mode when connecting a network that presents a captive portal.
+ */
+ @CaptivePortalMode
+ public static int getCaptivePortalMode(@NonNull Context context,
+ @CaptivePortalMode int def) {
+ return Settings.Global.getInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, def);
+ }
+
+ /**
+ * Set mode (to {@link Settings}) when connecting a network that presents a captive portal.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param mode The mode when connecting a network that presents a captive portal.
+ */
+ public static void setCaptivePortalMode(@NonNull Context context, @CaptivePortalMode int mode) {
+ if (!(mode == CAPTIVE_PORTAL_MODE_IGNORE
+ || mode == CAPTIVE_PORTAL_MODE_PROMPT
+ || mode == CAPTIVE_PORTAL_MODE_AVOID)) {
+ throw new IllegalArgumentException("Invalid captive portal mode");
+ }
+ Settings.Global.putInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, mode);
+ }
+
+ /**
+ * Get the global HTTP proxy applied to the device, or null if none.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The {@link ProxyInfo} which build from global http proxy settings.
+ */
+ @Nullable
+ public static ProxyInfo getGlobalProxy(@NonNull Context context) {
+ final String host = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST);
+ final int port = Settings.Global.getInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* def */);
+ final String exclusionList = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ final String pacFileUrl = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC);
+
+ if (TextUtils.isEmpty(host) && TextUtils.isEmpty(pacFileUrl)) {
+ return null; // No global proxy.
+ }
+
+ if (TextUtils.isEmpty(pacFileUrl)) {
+ return ProxyInfo.buildDirectProxy(
+ host, port, ProxyUtils.exclusionStringAsList(exclusionList));
+ } else {
+ return ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
+ }
+ }
+
+ /**
+ * Set global http proxy settings from given {@link ProxyInfo}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from
+ * {@link ProxyInfo#buildPacProxy(Uri)} or
+ * {@link ProxyInfo#buildDirectProxy(String, int, List)}
+ */
+ public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) {
+ final String host = proxyInfo.getHost();
+ final int port = proxyInfo.getPort();
+ final String exclusionList = proxyInfo.getExclusionListAsString();
+ final String pacFileUrl = proxyInfo.getPacFileUrl().toString();
+
+ if (TextUtils.isEmpty(pacFileUrl)) {
+ Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclusionList);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
+ } else {
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
+ Settings.Global.putInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
+ }
+ }
+
+ /**
+ * Clear all global http proxy settings.
+ *
+ * @param context The {@link Context} to set the setting.
+ */
+ public static void clearGlobalProxy(@NonNull Context context) {
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
+ Settings.Global.putInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
+ }
+
+ /**
+ * Get specific private dns provider name from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The specific private dns provider name, or null if no setting value.
+ */
+ @Nullable
+ public static String getPrivateDnsHostname(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER);
+ }
+
+ /**
+ * Set specific private dns provider name to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param specifier The specific private dns provider name.
+ */
+ public static void setPrivateDnsHostname(@NonNull Context context,
+ @Nullable String specifier) {
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier);
+ }
+
+ /**
+ * Get default private dns mode from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The default private dns mode.
+ */
+ @PrivateDnsMode
+ @NonNull
+ public static String getPrivateDnsDefaultMode(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE);
+ }
+
+ /**
+ * Set default private dns mode to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param mode The default private dns mode. This should be one of the PRIVATE_DNS_MODE_*
+ * constants.
+ */
+ public static void setPrivateDnsDefaultMode(@NonNull Context context,
+ @NonNull @PrivateDnsMode String mode) {
+ if (!(mode == PRIVATE_DNS_MODE_OFF
+ || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
+ || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
+ throw new IllegalArgumentException("Invalid private dns mode");
+ }
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE, mode);
+ }
+
+ /**
+ * Get duration (from {@link Settings}) to keep a PendingIntent-based request.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default duration if no setting value.
+ * @return The duration to keep a PendingIntent-based request.
+ */
+ @NonNull
+ public static Duration getConnectivityKeepPendingIntentDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Secure.getInt(context.getContentResolver(),
+ CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, (int) def.toMillis());
+ return Duration.ofMillis(duration);
+ }
+
+ /**
+ * Set duration (to {@link Settings}) to keep a PendingIntent-based request.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The duration to keep a PendingIntent-based request.
+ */
+ public static void setConnectivityKeepPendingIntentDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.toMillis();
+ if (time < 0) {
+ throw new IllegalArgumentException("Invalid duration.");
+ }
+ Settings.Secure.putInt(
+ context.getContentResolver(), CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, time);
+ }
+
+ /**
+ * Read from {@link Settings} whether the mobile data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return Whether the mobile data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static boolean getMobileDataAlwaysOn(@NonNull Context context, boolean def) {
+ final int enable = Settings.Global.getInt(
+ context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (def ? 1 : 0));
+ return (enable != 0) ? true : false;
+ }
+
+ /**
+ * Write into {@link Settings} whether the mobile data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param enable Whether the mobile data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static void setMobileDataAlwaysOn(@NonNull Context context, boolean enable) {
+ Settings.Global.putInt(
+ context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (enable ? 1 : 0));
+ }
+
+ /**
+ * Read from {@link Settings} whether the wifi data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return Whether the wifi data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static boolean getWifiAlwaysRequested(@NonNull Context context, boolean def) {
+ final int enable = Settings.Global.getInt(
+ context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (def ? 1 : 0));
+ return (enable != 0) ? true : false;
+ }
+
+ /**
+ * Write into {@link Settings} whether the wifi data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param enable Whether the wifi data connection should remain active even when higher
+ * priority networks are active
+ */
+ public static void setWifiAlwaysRequested(@NonNull Context context, boolean enable) {
+ Settings.Global.putInt(
+ context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (enable ? 1 : 0));
+ }
+
+ /**
+ * Get avoid bad wifi setting from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The setting whether to automatically switch away from wifi networks that lose
+ * internet access.
+ */
+ @NetworkAvoidBadWifi
+ public static int getNetworkAvoidBadWifi(@NonNull Context context) {
+ final String setting =
+ Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI);
+ if ("0".equals(setting)) {
+ return NETWORK_AVOID_BAD_WIFI_IGNORE;
+ } else if ("1".equals(setting)) {
+ return NETWORK_AVOID_BAD_WIFI_AVOID;
+ } else {
+ return NETWORK_AVOID_BAD_WIFI_PROMPT;
+ }
+ }
+
+ /**
+ * Set avoid bad wifi setting to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param value Whether to automatically switch away from wifi networks that lose internet
+ * access.
+ */
+ public static void setNetworkAvoidBadWifi(@NonNull Context context,
+ @NetworkAvoidBadWifi int value) {
+ final String setting;
+ if (value == NETWORK_AVOID_BAD_WIFI_IGNORE) {
+ setting = "0";
+ } else if (value == NETWORK_AVOID_BAD_WIFI_AVOID) {
+ setting = "1";
+ } else if (value == NETWORK_AVOID_BAD_WIFI_PROMPT) {
+ setting = null;
+ } else {
+ throw new IllegalArgumentException("Invalid avoid bad wifi setting");
+ }
+ Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting);
+ }
+
+ /**
+ * Get network metered multipath preference from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The network metered multipath preference which should be one of
+ * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value specified
+ * by config_networkMeteredMultipathPreference is used.
+ */
+ @Nullable
+ public static String getNetworkMeteredMultipathPreference(@NonNull Context context) {
+ return Settings.Global.getString(
+ context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE);
+ }
+
+ /**
+ * Set network metered multipath preference to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param preference The network metered multipath preference which should be one of
+ * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value
+ * specified by config_networkMeteredMultipathPreference is used.
+ */
+ public static void setNetworkMeteredMultipathPreference(@NonNull Context context,
+ @NonNull @MultipathPreference String preference) {
+ if (!(Integer.valueOf(preference) == MULTIPATH_PREFERENCE_HANDOVER
+ || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_RELIABILITY
+ || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_PERFORMANCE)) {
+ throw new IllegalArgumentException("Invalid private dns mode");
+ }
+ Settings.Global.putString(
+ context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference);
+ }
+
+ /**
+ * Get the list of apps(from {@link Settings}) that should go on cellular networks in preference
+ * even when higher-priority networks are connected.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return A list of apps that should go on cellular networks in preference even when
+ * higher-priority networks are connected or null if no setting value.
+ */
+ @Nullable
+ public static String getMobileDataPreferredApps(@NonNull Context context) {
+ return Settings.Secure.getString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS);
+ }
+
+ /**
+ * Set the list of apps(to {@link Settings}) that should go on cellular networks in preference
+ * even when higher-priority networks are connected.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param list A list of apps that should go on cellular networks in preference even when
+ * higher-priority networks are connected.
+ */
+ public static void setMobileDataPreferredApps(@NonNull Context context, @Nullable String list) {
+ Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS, list);
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
index 1f66e18..f9d3994 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
@@ -46,4 +46,6 @@
void onRemoveKeepalivePacketFilter(int slot);
void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel);
void onQosCallbackUnregistered(int qosCallbackId);
+ void onNetworkCreated();
+ void onNetworkDisconnected();
}
diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
index c5464d3..cbd6193 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -22,6 +22,7 @@
import android.net.NetworkScore;
import android.net.QosSession;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
/**
* Interface for NetworkAgents to send network properties.
@@ -37,6 +38,7 @@
void sendSocketKeepaliveEvent(int slot, int reason);
void sendUnderlyingNetworks(in @nullable List<Network> networks);
void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes);
+ void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes);
void sendQosSessionLost(int qosCallbackId, in QosSession session);
void sendQosCallbackError(int qosCallbackId, int exceptionType);
}
diff --git a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
index 67d2d405..a6de173 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
@@ -22,15 +22,16 @@
* A callback registered with connectivity by network providers together with
* a NetworkOffer.
*
- * When the offer is needed to satisfy some application or system component,
- * connectivity will call onOfferNeeded on this callback. When this happens,
- * the provider should try and bring up the network.
+ * When the network for this offer is needed to satisfy some application or
+ * system component, connectivity will call onNetworkNeeded on this callback.
+ * When this happens, the provider should try and bring up the network.
*
- * When the offer is no longer needed, for example because the application has
- * withdrawn the request or if the request is being satisfied by a network
- * that this offer will never be able to beat, connectivity calls
- * onOfferUnneeded. When this happens, the provider should stop trying to
- * bring up the network, or tear it down if it has already been brought up.
+ * When the network for this offer is no longer needed, for example because
+ * the application has withdrawn the request or if the request is being
+ * satisfied by a network that this offer will never be able to beat,
+ * connectivity calls onNetworkUnneeded. When this happens, the provider
+ * should stop trying to bring up the network, or tear it down if it has
+ * already been brought up.
*
* When NetworkProvider#offerNetwork is called, the provider can expect to
* immediately receive all requests that can be fulfilled by that offer and
@@ -38,25 +39,25 @@
* request is currently outstanding, because no requests have been made that
* can be satisfied by this offer, or because all such requests are already
* satisfied by a better network.
- * onOfferNeeded can be called at any time after registration and until the
+ * onNetworkNeeded can be called at any time after registration and until the
* offer is withdrawn with NetworkProvider#unofferNetwork is called. This
* typically happens when a new network request is filed by an application,
* or when the network satisfying a request disconnects and this offer now
- * stands a chance to be the best network for it.
+ * stands a chance to supply the best network for it.
*
* @hide
*/
oneway interface INetworkOfferCallback {
/**
- * Informs the registrant that the offer is needed to fulfill this request.
+ * Called when a network for this offer is needed to fulfill this request.
* @param networkRequest the request to satisfy
* @param providerId the ID of the provider currently satisfying
* this request, or NetworkProvider.ID_NONE if none.
*/
- void onOfferNeeded(in NetworkRequest networkRequest, int providerId);
+ void onNetworkNeeded(in NetworkRequest networkRequest, int providerId);
/**
- * Informs the registrant that the offer is no longer needed to fulfill this request.
+ * Informs the registrant that the offer is no longer valuable to fulfill this request.
*/
- void onOfferUnneeded(in NetworkRequest networkRequest);
+ void onNetworkUnneeded(in NetworkRequest networkRequest);
}
diff --git a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
index 91c7575..c973541 100644
--- a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
+++ b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
@@ -19,6 +19,7 @@
import android.os.Bundle;
import android.net.QosSession;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
/**
* AIDL interface for QosCallback
@@ -29,6 +30,8 @@
{
void onQosEpsBearerSessionAvailable(in QosSession session,
in EpsBearerQosSessionAttributes attributes);
+ void onNrQosSessionAvailable(in QosSession session,
+ in NrQosSessionAttributes attributes);
void onQosSessionLost(in QosSession session);
void onError(in int type);
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index b3d9616..6b55bb7 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -32,6 +32,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -361,6 +362,22 @@
*/
public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
+ /**
+ * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native
+ * network was created and the Network object is now valid.
+ *
+ * @hide
+ */
+ public static final int CMD_NETWORK_CREATED = BASE + 22;
+
+ /**
+ * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native
+ * network was destroyed.
+ *
+ * @hide
+ */
+ public static final int CMD_NETWORK_DISCONNECTED = BASE + 23;
+
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
config.legacyTypeName, config.legacySubTypeName);
@@ -560,6 +577,14 @@
msg.arg1 /* QoS callback id */);
break;
}
+ case CMD_NETWORK_CREATED: {
+ onNetworkCreated();
+ break;
+ }
+ case CMD_NETWORK_DISCONNECTED: {
+ onNetworkDisconnected();
+ break;
+ }
}
}
}
@@ -700,6 +725,16 @@
mHandler.sendMessage(mHandler.obtainMessage(
CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null));
}
+
+ @Override
+ public void onNetworkCreated() {
+ mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_CREATED));
+ }
+
+ @Override
+ public void onNetworkDisconnected() {
+ mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DISCONNECTED));
+ }
}
/**
@@ -1010,6 +1045,17 @@
}
/**
+ * Called when ConnectivityService has successfully created this NetworkAgent's native network.
+ */
+ public void onNetworkCreated() {}
+
+
+ /**
+ * Called when ConnectivityService has successfully destroy this NetworkAgent's native network.
+ */
+ public void onNetworkDisconnected() {}
+
+ /**
* Requests that the network hardware send the specified packet at the specified interval.
*
* @param slot the hardware slot on which to start the keepalive.
@@ -1160,29 +1206,37 @@
/**
- * Sends the attributes of Eps Bearer Qos Session back to the Application
+ * Sends the attributes of Qos Session back to the Application
*
* @param qosCallbackId the callback id that the session belongs to
- * @param sessionId the unique session id across all Eps Bearer Qos Sessions
- * @param attributes the attributes of the Eps Qos Session
+ * @param sessionId the unique session id across all Qos Sessions
+ * @param attributes the attributes of the Qos Session
*/
public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId,
- @NonNull final EpsBearerQosSessionAttributes attributes) {
+ @NonNull final QosSessionAttributes attributes) {
Objects.requireNonNull(attributes, "The attributes must be non-null");
- queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
- new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
- attributes));
+ if (attributes instanceof EpsBearerQosSessionAttributes) {
+ queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
+ new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
+ (EpsBearerQosSessionAttributes)attributes));
+ } else if (attributes instanceof NrQosSessionAttributes) {
+ queueOrSendMessage(ra -> ra.sendNrQosSessionAvailable(qosCallbackId,
+ new QosSession(sessionId, QosSession.TYPE_NR_BEARER),
+ (NrQosSessionAttributes)attributes));
+ }
}
/**
- * Sends event that the Eps Qos Session was lost.
+ * Sends event that the Qos Session was lost.
*
* @param qosCallbackId the callback id that the session belongs to
- * @param sessionId the unique session id across all Eps Bearer Qos Sessions
+ * @param sessionId the unique session id across all Qos Sessions
+ * @param qosSessionType the session type {@code QosSesson#QosSessionType}
*/
- public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) {
+ public final void sendQosSessionLost(final int qosCallbackId,
+ final int sessionId, final int qosSessionType) {
queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId,
- new QosSession(sessionId, QosSession.TYPE_EPS_BEARER)));
+ new QosSession(sessionId, qosSessionType)));
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
index d5b5c9b..d859022 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
@@ -170,10 +170,11 @@
/** @hide */
// TODO : make @SystemApi when the impl is complete
public interface NetworkOfferCallback {
- /** Called by the system when this offer is needed to satisfy some networking request. */
- void onOfferNeeded(@NonNull NetworkRequest request, int providerId);
- /** Called by the system when this offer is no longer needed. */
- void onOfferUnneeded(@NonNull NetworkRequest request);
+ /** Called by the system when a network for this offer is needed to satisfy some
+ * networking request. */
+ void onNetworkNeeded(@NonNull NetworkRequest request, int providerId);
+ /** Called by the system when this offer is no longer valuable for this request. */
+ void onNetworkUnneeded(@NonNull NetworkRequest request);
}
private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
@@ -187,14 +188,14 @@
}
@Override
- public void onOfferNeeded(final @NonNull NetworkRequest request,
+ public void onNetworkNeeded(final @NonNull NetworkRequest request,
final int providerId) {
- mExecutor.execute(() -> callback.onOfferNeeded(request, providerId));
+ mExecutor.execute(() -> callback.onNetworkNeeded(request, providerId));
}
@Override
- public void onOfferUnneeded(final @NonNull NetworkRequest request) {
- mExecutor.execute(() -> callback.onOfferUnneeded(request));
+ public void onNetworkUnneeded(final @NonNull NetworkRequest request) {
+ mExecutor.execute(() -> callback.onNetworkUnneeded(request));
}
}
@@ -213,41 +214,41 @@
}
/**
- * Register or update an offer for network with the passed caps and score.
+ * Register or update an offer for network with the passed capabilities and score.
*
- * A NetworkProvider's job is to provide networks. This function is how a provider tells the
+ * A NetworkProvider's role is to provide networks. This method is how a provider tells the
* connectivity stack what kind of network it may provide. The score and caps arguments act
- * as filters that the connectivity stack uses to tell when the offer is necessary. When an
- * offer might be advantageous over existing networks, the provider will receive a call to
- * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider
- * should then try to bring up this network. When an offer is no longer needed, the stack
- * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The
+ * as filters that the connectivity stack uses to tell when the offer is valuable. When an
+ * offer might be preferred over existing networks, the provider will receive a call to
+ * the associated callback's {@link NetworkOfferCallback#onNetworkNeeded} method. The provider
+ * should then try to bring up this network. When an offer is no longer useful, the stack
+ * will inform the provider by calling {@link NetworkOfferCallback#onNetworkUnneeded}. The
* provider should stop trying to bring up such a network, or disconnect it if it already has
* one.
*
- * The stack determines what offers are needed according to what networks are currently
+ * The stack determines what offers are valuable according to what networks are currently
* available to the system, and what networking requests are made by applications. If an
- * offer looks like it could be a better choice than any existing network for any particular
- * request, that's when the stack decides the offer is needed. If the current networking
- * requests are all satisfied by networks that this offer can't possibly be a better match
- * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider
- * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded}
- * is called.
+ * offer looks like it could connect a better network than any existing network for any
+ * particular request, that's when the stack decides the network is needed. If the current
+ * networking requests are all satisfied by networks that this offer couldn't possibly be a
+ * better match for, that's when the offer is no longer valuable. An offer starts out as
+ * unneeded ; the provider should not try to bring up the network until
+ * {@link NetworkOfferCallback#onNetworkNeeded} is called.
*
* Note that the offers are non-binding to the providers, in particular because providers
* often don't know if they will be able to bring up such a network at any given time. For
- * example, no wireless network may be in range when the offer is needed. This is fine and
- * expected ; the provider should simply continue to try to bring up the network and do so
+ * example, no wireless network may be in range when the offer would be valuable. This is fine
+ * and expected ; the provider should simply continue to try to bring up the network and do so
* if/when it becomes possible. In the mean time, the stack will continue to satisfy requests
* with the best network currently available, or if none, keep the apps informed that no
* network can currently satisfy this request. When/if the provider can bring up the network,
* the connectivity stack will match it against requests, and inform interested apps of the
* availability of this network. This may, in turn, render the offer of some other provider
- * unneeded if all requests it used to satisfy are now better served by this network.
+ * low-value if all requests it used to satisfy are now better served by this network.
*
* A network can become unneeded for a reason like the above : whether the provider managed
* to bring up the offered network after it became needed or not, some other provider may
- * bring up a better network than this one, making this offer unneeded. A network may also
+ * bring up a better network than this one, making this network unneeded. A network may also
* become unneeded if the application making the request withdrew it (for example, after it
* is done transferring data, or if the user canceled an operation).
*
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index 16a49bc..2679b62 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -42,6 +42,9 @@
* {@hide}
*/
public class NetworkUtils {
+ static {
+ System.loadLibrary("framework-connectivity-jni");
+ }
private static final String TAG = "NetworkUtils";
diff --git a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
index bdb4ad6..de0fc24 100644
--- a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
+++ b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import com.android.internal.annotations.VisibleForTesting;
@@ -84,6 +85,25 @@
}
/**
+ * Called when either the {@link NrQosSessionAttributes} has changed or on the first time
+ * the attributes have become available.
+ *
+ * @param session the session that is now available
+ * @param attributes the corresponding attributes of session
+ */
+ @Override
+ public void onNrQosSessionAvailable(@NonNull final QosSession session,
+ @NonNull final NrQosSessionAttributes attributes) {
+
+ mExecutor.execute(() -> {
+ final QosCallback callback = mCallback;
+ if (callback != null) {
+ callback.onQosSessionAvailable(session, attributes);
+ }
+ });
+ }
+
+ /**
* Called when the session is lost.
*
* @param session the session that was lost
diff --git a/packages/Connectivity/framework/src/android/net/QosSession.java b/packages/Connectivity/framework/src/android/net/QosSession.java
index 4f3bb77..93f2ff2 100644
--- a/packages/Connectivity/framework/src/android/net/QosSession.java
+++ b/packages/Connectivity/framework/src/android/net/QosSession.java
@@ -36,6 +36,11 @@
*/
public static final int TYPE_EPS_BEARER = 1;
+ /**
+ * The {@link QosSession} is a NR Session.
+ */
+ public static final int TYPE_NR_BEARER = 2;
+
private final int mSessionId;
private final int mSessionType;
@@ -100,6 +105,7 @@
*/
@IntDef(value = {
TYPE_EPS_BEARER,
+ TYPE_NR_BEARER,
})
@interface QosSessionType {}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index 9d1bb0f..b44128b 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -83,7 +83,6 @@
"service-connectivity-protos",
],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
}
@@ -100,7 +99,6 @@
],
libs: ["libprotobuf-java-nano"],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
}
@@ -115,7 +113,7 @@
],
jarjar_rules: "jarjar-rules.txt",
apex_available: [
- "//apex_available:platform",
+ "//apex_available:platform", // For arc-services
"com.android.tethering",
],
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 231babe..dd9fc2c 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -23,5 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.cellbroadcast",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
new file mode 100644
index 0000000..24d53ab
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -0,0 +1,60 @@
+<?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.
+ -->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/content_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:transitionGroup="true">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="180dp"
+ android:theme="@style/Theme.CollapsingToolbar.Settings">
+
+ <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout
+ android:id="@+id/collapsing_toolbar"
+ android:background="?android:attr/colorPrimary"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:maxLines="3"
+ app:contentScrim="?android:attr/colorPrimary"
+ app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
+ app:statusBarScrim="?android:attr/colorPrimary"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed"
+ app:expandedTitleMarginStart="18dp"
+ app:expandedTitleMarginEnd="18dp"
+ app:toolbarId="@id/action_bar">
+
+ <Toolbar
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?android:attr/actionBarTheme"
+ app:layout_collapseMode="pin"/>
+
+ </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout>
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
index e376930..c799b99 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
@@ -14,47 +14,23 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+<!-- The main content view -->
+<LinearLayout
android:id="@+id/content_parent"
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:transitionGroup="true">
-
- <com.google.android.material.appbar.AppBarLayout
- android:id="@+id/app_bar"
+ android:fitsSystemWindows="true"
+ android:transitionGroup="true"
+ android:orientation="vertical">
+ <Toolbar
+ android:id="@+id/action_bar"
+ style="?android:attr/actionBarStyle"
android:layout_width="match_parent"
- android:layout_height="180dp"
- android:theme="@style/Theme.CollapsingToolbar.Settings">
-
- <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout
- android:id="@+id/collapsing_toolbar"
- android:background="?android:attr/colorPrimary"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:maxLines="3"
- app:contentScrim="?android:attr/colorPrimary"
- app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
- app:statusBarScrim="?android:attr/colorPrimary"
- app:layout_scrollFlags="scroll|exitUntilCollapsed"
- app:expandedTitleMarginStart="18dp"
- app:expandedTitleMarginEnd="18dp"
- app:toolbarId="@id/action_bar">
-
- <Toolbar
- android:id="@+id/action_bar"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:theme="?android:attr/actionBarTheme"
- app:layout_collapseMode="pin"/>
-
- </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout>
- </com.google.android.material.appbar.AppBarLayout>
-
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/actionBarTheme" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
+ android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
deleted file mode 100644
index c799b99..0000000
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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.
--->
-<!-- The main content view -->
-<LinearLayout
- android:id="@+id/content_parent"
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fitsSystemWindows="true"
- android:transitionGroup="true"
- android:orientation="vertical">
- <Toolbar
- android:id="@+id/action_bar"
- style="?android:attr/actionBarStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="?android:attr/actionBarTheme" />
- <FrameLayout
- android:id="@+id/content_frame"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-</LinearLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index ad94cd03..957bac7 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -24,7 +24,6 @@
import android.widget.Toolbar;
import androidx.annotation.Nullable;
-import androidx.core.os.BuildCompat;
import androidx.fragment.app.FragmentActivity;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -41,15 +40,8 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // TODO(b/181723278): Update the version check after SDK for S is finalized
- // The collapsing toolbar is only supported if the android platform version is S or higher.
- // Otherwise the regular action bar will be shown.
- if (BuildCompat.isAtLeastS()) {
- super.setContentView(R.layout.collapsing_toolbar_base_layout);
- mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
- } else {
- super.setContentView(R.layout.toolbar_base_layout);
- }
+ super.setContentView(R.layout.collapsing_toolbar_base_layout);
+ mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
final Toolbar toolbar = findViewById(R.id.action_bar);
setActionBar(toolbar);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
new file mode 100644
index 0000000..c4c74ff
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.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.settingslib.collapsingtoolbar;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.Toolbar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
+/**
+ * A base fragment that has a collapsing toolbar layout for enabling the collapsing toolbar design.
+ */
+public abstract class CollapsingToolbarBaseFragment extends Fragment {
+
+ @Nullable
+ private CollapsingToolbarLayout mCollapsingToolbarLayout;
+ @NonNull
+ private Toolbar mToolbar;
+ @NonNull
+ private FrameLayout mContentFrameLayout;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.collapsing_toolbar_base_layout, container,
+ false);
+ mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
+ mToolbar = view.findViewById(R.id.action_bar);
+ mContentFrameLayout = view.findViewById(R.id.content_frame);
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ requireActivity().setActionBar(mToolbar);
+ }
+
+ /**
+ * Return the collapsing toolbar layout.
+ */
+ @Nullable
+ public CollapsingToolbarLayout getCollapsingToolbarLayout() {
+ return mCollapsingToolbarLayout;
+ }
+
+ /**
+ * Return the content frame layout.
+ */
+ @NonNull
+ public FrameLayout getContentFrameLayout() {
+ return mContentFrameLayout;
+ }
+}
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 8d9a562..ae9261c 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -63,7 +63,8 @@
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater.from(context).inflate(R.layout.main_switch_bar, this);
+ LayoutInflater.from(context).inflate(resourceId(context, "layout", "main_switch_bar"),
+ this);
setFocusable(true);
setClickable(true);
@@ -255,4 +256,8 @@
requestLayout();
}
+
+ private int resourceId(Context context, String type, String name) {
+ return context.getResources().getIdentifier(name, type, context.getPackageName());
+ }
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7186ec5..927e9db 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1272,6 +1272,22 @@
<!-- Button label for generic OK action [CHAR LIMIT=20] -->
<string name="okay">OK</string>
+ <!-- Label for the settings activity for controlling apps that can schedule alarms [CHAR LIMIT=30] -->
+ <string name="alarms_and_reminders_label">Alarms and reminders</string>
+ <!-- Label for the switch to toggler the permission for scheduling alarms [CHAR LIMIT=50] -->
+ <string name="alarms_and_reminders_switch_title">Allow to set alarms or reminders</string>
+ <!-- Title for the setting screen for controlling apps that can schedule alarms [CHAR LIMIT=30] -->
+ <string name="alarms_and_reminders_title">Alarms and reminders</string>
+ <!-- Description that appears below the alarms_and_reminders switch [CHAR LIMIT=NONE] -->
+ <string name="alarms_and_reminders_footer_title">
+ Allow this app to schedule alarms or other timing based events.
+ This will allow the app to wake up and run even when you are not using the device.
+ Note that revoking this permission may cause the app to malfunction, specifically any alarms
+ that the app has scheduled will no longer work.
+ </string>
+ <!-- Keywords for setting screen for controlling apps that can schedule alarms [CHAR LIMIT=100] -->
+ <string name="keywords_alarms_and_reminders">schedule, alarm, reminder, event</string>
+
<!-- Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] -->
<string name="zen_mode_enable_dialog_turn_on">Turn on</string>
<!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]-->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 8987968..a5da8b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -31,6 +31,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import com.android.settingslib.R;
@@ -198,4 +199,17 @@
}
return false;
}
+
+ /**
+ * Returns a boolean indicating whether a given package is a default browser.
+ *
+ * @param packageName a given package.
+ * @return true if the given package is default browser.
+ */
+ public static boolean isDefaultBrowser(Context context, String packageName) {
+ final String defaultBrowserPackage =
+ context.getPackageManager().getDefaultBrowserPackageNameAsUser(
+ UserHandle.myUserId());
+ return TextUtils.equals(packageName, defaultBrowserPackage);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index db9b83e..53920f0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -165,6 +165,8 @@
VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 7288371..4119dc9f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1881,6 +1881,12 @@
dumpSetting(s, p,
Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
SecureSettingsProto.Assist.GESTURE_SETUP_COMPLETE);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED,
+ SecureSettingsProto.Assist.TOUCH_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
+ SecureSettingsProto.Assist.LONG_PRESS_HOME_ENABLED);
p.end(assistToken);
final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
@@ -2150,6 +2156,9 @@
dumpSetting(s, p,
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
SecureSettingsProto.InputMethods.SHOW_IME_WITH_HARD_KEYBOARD);
+ dumpSetting(s, p,
+ Settings.Secure.DEFAULT_VOICE_INPUT_METHOD,
+ SecureSettingsProto.InputMethods.DEFAULT_VOICE_INPUT_METHOD);
p.end(inputMethodsToken);
dumpSetting(s, p,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
index caa9b4f..95c2d2e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
@@ -51,6 +51,15 @@
return Resources.ID_NULL;
}
+ /**
+ * @return resource id of the string to use for closing the detail panel. If
+ * {@code Resources.ID_NULL}, then use the default string:
+ * {@code com.android.systemui.R.string.quick_settings_done}
+ */
+ default int getDoneText() {
+ return Resources.ID_NULL;
+ }
+
void setToggleState(boolean state);
int getMetricsCategory();
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 730f24f..d82151d 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,6 +22,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <com.android.systemui.statusbar.charging.ChargingRippleView
+ android:id="@+id/wireless_charging_ripple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
<!-- Circle animation -->
<ImageView
android:id="@+id/wireless_charging_view"
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index be49e1f..886f98e 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -124,6 +124,7 @@
<attr name="darkIconTheme" format="reference" />
<attr name="wallpaperTextColor" format="reference|color" />
<attr name="wallpaperTextColorSecondary" format="reference|color" />
+ <attr name="wallpaperTextColorAccent" format="reference|color" />
<attr name="backgroundProtectedStyle" format="reference" />
<declare-styleable name="SmartReplyView">
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index fbe7175..6e61148 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -99,7 +99,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
+ internet,wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
</string>
<!-- The minimum number of tiles to display in QuickSettings -->
@@ -107,7 +107,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm,wallet
+ internet,wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm,wallet
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 935f025..3ca885a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -239,10 +239,8 @@
<string name="screenshot_edit_label">Edit</string>
<!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] -->
<string name="screenshot_edit_description">Edit screenshot</string>
- <!-- Label for UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=30] -->
- <string name="screenshot_scroll_label">Scroll</string>
- <!-- Content description UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=NONE] -->
- <string name="screenshot_scroll_description">Scroll screenshot</string>
+ <!-- Label for UI element which allows the user to capture additional off-screen content in a screenshot. [CHAR LIMIT=30] -->
+ <string name="screenshot_scroll_label">Capture more</string>
<!-- Content description indicating that tapping a button will dismiss the screenshots UI [CHAR LIMIT=NONE] -->
<string name="screenshot_dismiss_description">Dismiss screenshot</string>
<!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] -->
@@ -893,6 +891,8 @@
<string name="quick_settings_more_user_settings">User settings</string>
<!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="quick_settings_done">Done</string>
+ <!-- QuickSettings: Control panel: Label for button that dismisses user switcher control panel. [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_close_user_panel">Close</string>
<!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] -->
<string name="quick_settings_connected">Connected</string>
<!-- QuickSettings: Control panel: Label for connected device, showing remote device battery level. [CHAR LIMIT=NONE] -->
@@ -1284,6 +1284,9 @@
<!-- Disclosure at the bottom of Quick Settings that indicates that the device has a managed profile which can be monitored by the profile owner [CHAR LIMIT=100] -->
<string name="quick_settings_disclosure_named_managed_profile_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> may monitor network traffic in your work profile</string>
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device has a managed profile where network activity is visible to their IT admin [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_managed_profile_network_activity">Work profile network activity is visible to your IT admin</string>
+
<!-- Disclosure at the bottom of Quick Settings that indicates that a certificate authorithy is installed on this device and the traffic might be monitored [CHAR LIMIT=100] -->
<string name="quick_settings_disclosure_monitoring">Network may be monitored</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4a661dc..ecc1a5c 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -321,6 +321,7 @@
<item name="darkIconTheme">@style/DualToneDarkTheme</item>
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
+ <item name="wallpaperTextColorAccent">@*android:color/system_accent1_100</item>
<item name="android:colorError">@*android:color/error_color_material_dark</item>
<item name="android:colorControlHighlight">@*android:color/primary_text_material_dark</item>
<item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
@@ -337,6 +338,7 @@
<style name="Theme.SystemUI.Light">
<item name="wallpaperTextColor">@*android:color/primary_text_material_light</item>
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item>
+ <item name="wallpaperTextColorAccent">@*android:color/system_accent2_600</item>
<item name="android:colorError">@*android:color/error_color_material_light</item>
<item name="android:colorControlHighlight">#40000000</item>
<item name="shadowRadius">0</item>
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index d2bff18..b2bf6da 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -16,9 +16,9 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="120dp"
- android:minHeight="54dp"
+ android:minHeight="50dp"
android:minResizeWidth="60dp"
- android:minResizeHeight="54dp"
+ android:minResizeHeight="50dp"
android:maxResizeHeight="207dp"
android:updatePeriodMillis="60000"
android:description="@string/people_tile_description"
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 3f0e3eb..ab219f3 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -131,7 +131,7 @@
private void initColors() {
mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
- com.android.systemui.R.attr.wallpaperTextColor);
+ com.android.systemui.R.attr.wallpaperTextColorAccent);
mView.setColors(mDozingColor, mLockScreenColor);
mView.animateDoze(mIsDozing, false);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index bb1d972..cfef6cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -160,11 +160,18 @@
*
* @param separator Separator between different parts of the text
*/
- private CarrierTextManager(Context context, CharSequence separator, boolean showAirplaneMode,
- boolean showMissingSim, @Nullable WifiManager wifiManager,
- TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager,
- WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor,
- @Background Executor bgExecutor, KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ private CarrierTextManager(
+ Context context,
+ CharSequence separator,
+ boolean showAirplaneMode,
+ boolean showMissingSim,
+ @Nullable WifiManager wifiManager,
+ TelephonyManager telephonyManager,
+ TelephonyListenerManager telephonyListenerManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
@@ -633,12 +640,15 @@
private boolean mShowMissingSim;
@Inject
- public Builder(Context context, @Main Resources resources,
+ public Builder(
+ Context context,
+ @Main Resources resources,
@Nullable WifiManager wifiManager,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
- @Main Executor mainExecutor, @Background Executor bgExecutor,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mSeparator = resources.getString(
@@ -668,8 +678,8 @@
public CarrierTextManager build() {
return new CarrierTextManager(
mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
- mTelephonyManager, mTelephonyListenerManager,
- mWakefulnessLifecycle, mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
+ mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
+ mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
}
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 561ea40..568bea0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -70,7 +70,7 @@
void onThemeChanged() {
TypedArray array = mContext.obtainStyledAttributes(new int[] {
- android.R.attr.textColor
+ android.R.attr.textColorPrimary
});
ColorStateList newTextColors = ColorStateList.valueOf(array.getColor(0, Color.RED));
array.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 378907c..e274843 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -62,7 +62,8 @@
mBgProtection = findViewById(R.id.udfps_keyguard_fp_bg);
- mWallpaperTexColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
+ mWallpaperTexColor = Utils.getColorAttrDefaultColor(mContext,
+ R.attr.wallpaperTextColorAccent);
mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
android.R.attr.textColorPrimary);
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 2569f7c..f7beaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -26,7 +26,6 @@
import android.util.Log;
import android.util.Slog;
import android.view.Gravity;
-import android.view.View;
import android.view.WindowManager;
/**
@@ -98,8 +97,8 @@
private final Handler mHandler;
private int mGravity;
- private View mView;
- private View mNextView;
+ private WirelessChargingLayout mView;
+ private WirelessChargingLayout mNextView;
private WindowManager mWM;
private Callback mCallback;
@@ -112,7 +111,7 @@
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
final WindowManager.LayoutParams params = mParams;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.format = PixelFormat.TRANSLUCENT;
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index e8407f0..ce0b514 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -20,6 +20,7 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.drawable.Animatable;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -29,8 +30,10 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.charging.ChargingRippleView;
import java.text.NumberFormat;
@@ -38,7 +41,9 @@
* @hide
*/
public class WirelessChargingLayout extends FrameLayout {
- public final static int UNKNOWN_BATTERY_LEVEL = -1;
+ public static final int UNKNOWN_BATTERY_LEVEL = -1;
+ private static final long RIPPLE_ANIMATION_DURATION = 2000;
+ private ChargingRippleView mRippleView;
public WirelessChargingLayout(Context context) {
super(context);
@@ -120,6 +125,8 @@
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
+ mRippleView = findViewById(R.id.wireless_charging_ripple);
+
if (!showTransmittingBatteryLevel) {
chargingAnimation.start();
animatorSet.start();
@@ -195,4 +202,21 @@
animatorSetTransmitting.start();
animatorSetIcon.start();
}
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mRippleView != null) {
+ int width = getMeasuredWidth();
+ int height = getMeasuredHeight();
+ mRippleView.setColor(
+ Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor());
+ mRippleView.setOrigin(new PointF(width / 2, height / 2));
+ mRippleView.setRadius(Math.max(width, height) * 0.5f);
+ mRippleView.setDuration(RIPPLE_ANIMATION_DURATION);
+ mRippleView.startRipple();
+ }
+
+ super.onLayout(changed, left, top, right, bottom);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 52c9f16..b7f6e2b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -186,7 +186,7 @@
new TriggerSensor(
findSensorWithType(config.quickPickupSensorType()),
Settings.Secure.DOZE_QUICK_PICKUP_GESTURE,
- false /* setting default */,
+ true /* setting default */,
config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser())
&& udfpsEnrolled,
DozeLog.REASON_SENSOR_QUICK_PICKUP,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 9d43e0c..c8dfde1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -209,6 +209,8 @@
private @TransitionMode int mNavigationBarMode;
private ContentResolver mContentResolver;
private boolean mAssistantAvailable;
+ private boolean mLongPressHomeEnabled;
+ private boolean mAssistantTouchGestureEnabled;
private int mDisabledFlags1;
private int mDisabledFlags2;
@@ -309,7 +311,7 @@
// Send the assistant availability upon connection
if (isConnected) {
- sendAssistantAvailability(mAssistantAvailable);
+ updateAssistantEntrypoints();
}
}
@@ -404,12 +406,7 @@
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
- boolean available = mAssistManagerLazy.get()
- .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
- if (mAssistantAvailable != available) {
- sendAssistantAvailability(available);
- mAssistantAvailable = available;
- }
+ updateAssistantEntrypoints();
}
};
@@ -531,6 +528,13 @@
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED),
+ false, mAssistContentObserver, UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED),
+ false, mAssistContentObserver, UserHandle.USER_ALL);
+ updateAssistantEntrypoints();
if (savedState != null) {
mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0);
@@ -823,7 +827,7 @@
|| mNavigationBarView.getHomeButton().getCurrentView() == null) {
return;
}
- if (mHomeButtonLongPressDurationMs.isPresent()) {
+ if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) {
mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(false);
mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
mNavigationBarView.getHomeButton().setOnLongClickListener(null);
@@ -845,6 +849,8 @@
pw.println(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
pw.println(" mCurrentRotation=" + mCurrentRotation);
pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
+ pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled);
+ pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled);
if (mNavigationBarView != null) {
pw.println(" mNavigationBarWindowState="
@@ -1206,9 +1212,11 @@
return true;
}
}
- mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
- mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
- });
+ if (mLongPressHomeEnabled) {
+ mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
+ });
+ }
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
@@ -1480,15 +1488,23 @@
| (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
}
- private void sendAssistantAvailability(boolean available) {
+ private void updateAssistantEntrypoints() {
+ mAssistantAvailable = mAssistManagerLazy.get()
+ .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+ mLongPressHomeEnabled = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, 1) != 0;
+ mAssistantTouchGestureEnabled = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, 1) != 0;
if (mOverviewProxyService.getProxy() != null) {
try {
- mOverviewProxyService.getProxy().onAssistantAvailable(available
+ mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable
+ && mAssistantTouchGestureEnabled
&& QuickStepContract.isGesturalMode(mNavBarMode));
} catch (RemoteException e) {
Log.w(TAG, "Unable to send assistant availability data to launcher");
}
}
+ reconfigureHomeLongClick();
}
// ----- Methods that DisplayNavigationBarController talks to -----
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 5bc1280..440c5ef 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -175,7 +175,7 @@
/** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */
public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
- int appWidgetId) {
+ int appWidgetId, Uri contactUri) {
// Write relevant persisted storage.
SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
Context.MODE_PRIVATE);
@@ -186,27 +186,24 @@
widgetEditor.apply();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
- editor.putString(String.valueOf(appWidgetId), key.getShortcutId());
+ String contactUriString = contactUri == null ? EMPTY_STRING : contactUri.toString();
+ editor.putString(String.valueOf(appWidgetId), contactUriString);
// Don't overwrite existing widgets with the same key.
- Set<String> storedWidgetIds = new HashSet<>(
- sp.getStringSet(key.toString(), new HashSet<>()));
- storedWidgetIds.add(String.valueOf(appWidgetId));
- editor.putStringSet(key.toString(), storedWidgetIds);
+ addAppWidgetIdForKey(sp, editor, appWidgetId, key.toString());
+ addAppWidgetIdForKey(sp, editor, appWidgetId, contactUriString);
editor.apply();
}
/** Removes stored data when tile is deleted. */
public static void removeSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
- int widgetId) {
+ int widgetId, String contactUriString) {
// Delete widgetId mapping to key.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
- Set<String> storedWidgetIds = new HashSet<>(
- sp.getStringSet(key.toString(), new HashSet<>()));
- storedWidgetIds.remove(String.valueOf(widgetId));
- editor.putStringSet(key.toString(), storedWidgetIds);
editor.remove(String.valueOf(widgetId));
+ removeAppWidgetIdForKey(sp, editor, widgetId, key.toString());
+ removeAppWidgetIdForKey(sp, editor, widgetId, contactUriString);
editor.apply();
// Delete all data specifically mapped to widgetId.
@@ -219,6 +216,23 @@
widgetEditor.apply();
}
+ private static void addAppWidgetIdForKey(SharedPreferences sp, SharedPreferences.Editor editor,
+ int widgetId, String storageKey) {
+ Set<String> storedWidgetIdsByKey = new HashSet<>(
+ sp.getStringSet(storageKey, new HashSet<>()));
+ storedWidgetIdsByKey.add(String.valueOf(widgetId));
+ editor.putStringSet(storageKey, storedWidgetIdsByKey);
+ }
+
+ private static void removeAppWidgetIdForKey(SharedPreferences sp,
+ SharedPreferences.Editor editor,
+ int widgetId, String storageKey) {
+ Set<String> storedWidgetIds = new HashSet<>(
+ sp.getStringSet(storageKey, new HashSet<>()));
+ storedWidgetIds.remove(String.valueOf(widgetId));
+ editor.putStringSet(storageKey, storedWidgetIds);
+ }
+
/** Augments a single {@link PeopleSpaceTile} with notification content, if one is present. */
public static PeopleSpaceTile augmentSingleTileFromVisibleNotifications(Context context,
PeopleSpaceTile tile, NotificationEntryManager notificationEntryManager) {
@@ -256,7 +270,7 @@
PeopleSpaceTile tile, Map<PeopleTileKey, NotificationEntry> visibleNotifications) {
PeopleTileKey key = new PeopleTileKey(
tile.getId(), getUserId(tile), tile.getPackageName());
-
+ // TODO: Match missed calls with matching Uris in addition to keys.
if (!visibleNotifications.containsKey(key)) {
if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key.toString());
return tile;
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 776e8a2..5be2d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -16,17 +16,23 @@
package com.android.systemui.people.widget;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.Notification.EXTRA_PEOPLE_LIST;
+
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotification;
+import static com.android.systemui.people.PeopleSpaceUtils.getMessagingStyleMessages;
import static com.android.systemui.people.PeopleSpaceUtils.getStoredWidgetIds;
import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView;
import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.app.Person;
@@ -39,6 +45,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -54,16 +61,20 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
@@ -83,11 +94,19 @@
private SharedPreferences mSharedPrefs;
private PeopleManager mPeopleManager;
private NotificationEntryManager mNotificationEntryManager;
+ private PackageManager mPackageManager;
public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
@GuardedBy("mLock")
public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener>
mListeners = new HashMap<>();
+ @GuardedBy("mLock")
+ // Map of notification key mapped to widget IDs previously updated by the contact Uri field.
+ // This is required because on notification removal, the contact Uri field is stripped and we
+ // only have the notification key to determine which widget IDs should be updated.
+ private Map<String, Set<String>> mNotificationKeyToWidgetIdsMatchedByUri = new HashMap<>();
+ private boolean mIsForTesting;
+
@Inject
public PeopleSpaceWidgetManager(Context context) {
if (DEBUG) Log.d(TAG, "constructor");
@@ -99,6 +118,7 @@
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPeopleManager = mContext.getSystemService(PeopleManager.class);
mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
+ mPackageManager = mContext.getPackageManager();
}
/**
@@ -108,12 +128,15 @@
protected void setAppWidgetManager(
AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
PeopleManager peopleManager, LauncherApps launcherApps,
- NotificationEntryManager notificationEntryManager) {
+ NotificationEntryManager notificationEntryManager, PackageManager packageManager,
+ boolean isForTesting) {
mAppWidgetManager = appWidgetManager;
mIPeopleManager = iPeopleManager;
mPeopleManager = peopleManager;
mLauncherApps = launcherApps;
mNotificationEntryManager = notificationEntryManager;
+ mPackageManager = packageManager;
+ mIsForTesting = isForTesting;
}
/**
@@ -222,6 +245,16 @@
public void updateWidgetsWithNotificationChanged(StatusBarNotification sbn,
PeopleSpaceUtils.NotificationAction notificationAction) {
if (DEBUG) Log.d(TAG, "updateWidgetsWithNotificationChanged called");
+ if (mIsForTesting) {
+ updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction);
+ return;
+ }
+ ThreadUtils.postOnBackgroundThread(
+ () -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction));
+ }
+
+ private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
+ PeopleSpaceUtils.NotificationAction action) {
try {
String sbnShortcutId = sbn.getShortcutId();
if (sbnShortcutId == null) {
@@ -235,23 +268,175 @@
Log.d(TAG, "No app widget ids returned");
return;
}
+ PeopleTileKey key = new PeopleTileKey(
+ sbnShortcutId,
+ sbn.getUser().getIdentifier(),
+ sbn.getPackageName());
+ if (!key.isValid()) {
+ Log.d(TAG, "Invalid key");
+ return;
+ }
synchronized (mLock) {
- PeopleTileKey key = new PeopleTileKey(
- sbnShortcutId,
- UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(),
- sbn.getPackageName());
- Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, key);
- for (String widgetIdString : storedWidgetIds) {
- int widgetId = Integer.parseInt(widgetIdString);
- if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey());
- updateStorageAndViewWithNotificationData(sbn, notificationAction, widgetId);
- }
+ // First, update People Tiles associated with the Notification's package/shortcut.
+ Set<String> tilesUpdatedByKey = getStoredWidgetIds(mSharedPrefs, key);
+ updateWidgetIdsForNotificationAction(tilesUpdatedByKey, sbn, action);
+
+ // Then, update People Tiles across other packages that use the same Uri.
+ updateTilesByUri(key, sbn, action);
}
} catch (Exception e) {
Log.e(TAG, "Exception: " + e);
}
}
+ /** Updates {@code widgetIdsToUpdate} with {@code action}. */
+ private void updateWidgetIdsForNotificationAction(Set<String> widgetIdsToUpdate,
+ StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction action) {
+ for (String widgetIdString : widgetIdsToUpdate) {
+ int widgetId = Integer.parseInt(widgetIdString);
+ PeopleSpaceTile storedTile = getTileForExistingWidget(widgetId);
+ if (storedTile == null) {
+ if (DEBUG) Log.d(TAG, "Could not find stored tile for notification");
+ continue;
+ }
+ if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey());
+ updateStorageAndViewWithNotificationData(sbn, action, widgetId,
+ storedTile);
+ }
+ }
+
+ /**
+ * Updates tiles with matched Uris, dependent on the {@code action}.
+ *
+ * <p>If the notification was added, adds the notification based on the contact Uri within
+ * {@code sbn}.
+ * <p>If the notification was removed, removes the notification based on the in-memory map of
+ * widgets previously updated by Uri (since the contact Uri is stripped from the {@code sbn}).
+ */
+ private void updateTilesByUri(PeopleTileKey key, StatusBarNotification sbn,
+ PeopleSpaceUtils.NotificationAction action) {
+ if (action.equals(PeopleSpaceUtils.NotificationAction.POSTED)) {
+ Set<String> widgetIdsUpdatedByUri = supplementTilesByUri(sbn, action, key);
+ if (widgetIdsUpdatedByUri != null && !widgetIdsUpdatedByUri.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Added due to uri: " + widgetIdsUpdatedByUri);
+ mNotificationKeyToWidgetIdsMatchedByUri.put(sbn.getKey(), widgetIdsUpdatedByUri);
+ }
+ } else {
+ // Remove the notification on any widgets where the notification was added
+ // purely based on the Uri.
+ Set<String> widgetsPreviouslyUpdatedByUri =
+ mNotificationKeyToWidgetIdsMatchedByUri.remove(sbn.getKey());
+ if (widgetsPreviouslyUpdatedByUri != null && !widgetsPreviouslyUpdatedByUri.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Remove due to uri: " + widgetsPreviouslyUpdatedByUri);
+ updateWidgetIdsForNotificationAction(widgetsPreviouslyUpdatedByUri, sbn,
+ action);
+ }
+ }
+ }
+
+ /**
+ * Retrieves from storage any tiles with the same contact Uri as linked via the {@code sbn}.
+ * Supplements the tiles with the notification content only if they still have {@link
+ * android.Manifest.permission.READ_CONTACTS} permission.
+ */
+ @Nullable
+ private Set<String> supplementTilesByUri(StatusBarNotification sbn,
+ PeopleSpaceUtils.NotificationAction notificationAction, PeopleTileKey key) {
+ if (!shouldMatchNotificationByUri(sbn)) {
+ if (DEBUG) Log.d(TAG, "Should not supplement conversation");
+ return null;
+ }
+
+ // Try to get the Contact Uri from the Missed Call notification directly.
+ String contactUri = getContactUri(sbn);
+ if (contactUri == null) {
+ if (DEBUG) Log.d(TAG, "No contact uri");
+ return null;
+ }
+
+ // Supplement any tiles with the same Uri.
+ Set<String> storedWidgetIdsByUri =
+ new HashSet<>(mSharedPrefs.getStringSet(contactUri, new HashSet<>()));
+ if (storedWidgetIdsByUri.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No tiles for contact");
+ return null;
+ }
+
+ if (mPackageManager.checkPermission(READ_CONTACTS,
+ sbn.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+ if (DEBUG) Log.d(TAG, "Notifying app missing permissions");
+ return null;
+ }
+
+ Set<String> widgetIdsUpdatedByUri = new HashSet<>();
+ for (String widgetIdString : storedWidgetIdsByUri) {
+ int widgetId = Integer.parseInt(widgetIdString);
+ PeopleSpaceTile storedTile = getTileForExistingWidget(widgetId);
+ // Don't update a widget already updated.
+ if (key.equals(new PeopleTileKey(storedTile))) {
+ continue;
+ }
+ if (storedTile == null || mPackageManager.checkPermission(READ_CONTACTS,
+ storedTile.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+ if (DEBUG) Log.d(TAG, "Cannot supplement tile: " + storedTile.getUserName());
+ continue;
+ }
+ if (DEBUG) Log.d(TAG, "Adding notification by uri: " + sbn.getKey());
+ updateStorageAndViewWithNotificationData(sbn, notificationAction,
+ widgetId, storedTile);
+ widgetIdsUpdatedByUri.add(String.valueOf(widgetId));
+ }
+ return widgetIdsUpdatedByUri;
+ }
+
+ /**
+ * Try to retrieve a valid Uri via {@code sbn}, falling back to the {@code
+ * contactUriFromShortcut} if valid.
+ */
+ @Nullable
+ private String getContactUri(StatusBarNotification sbn) {
+ // First, try to get a Uri from the Person directly set on the Notification.
+ ArrayList<Person> people = sbn.getNotification().extras.getParcelableArrayList(
+ EXTRA_PEOPLE_LIST);
+ if (people != null && people.get(0) != null) {
+ String contactUri = people.get(0).getUri();
+ if (contactUri != null && !contactUri.isEmpty()) {
+ return contactUri;
+ }
+ }
+
+ // Then, try to get a Uri from the Person set on the Notification message.
+ List<Notification.MessagingStyle.Message> messages =
+ getMessagingStyleMessages(sbn.getNotification());
+ if (messages != null && !messages.isEmpty()) {
+ Notification.MessagingStyle.Message message = messages.get(0);
+ Person sender = message.getSenderPerson();
+ if (sender != null && sender.getUri() != null && !sender.getUri().isEmpty()) {
+ return sender.getUri();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether a notification should be matched to other Tiles by Uri.
+ *
+ * <p>Currently only matches missed calls.
+ */
+ private boolean shouldMatchNotificationByUri(StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ if (notification == null) {
+ if (DEBUG) Log.d(TAG, "Notification is null");
+ return false;
+ }
+ if (!Objects.equals(notification.category, CATEGORY_MISSED_CALL)) {
+ if (DEBUG) Log.d(TAG, "Not missed call");
+ return false;
+ }
+ return true;
+ }
+
/**
* Update the tiles associated with the incoming conversation update.
*/
@@ -309,16 +494,11 @@
private void updateStorageAndViewWithNotificationData(
StatusBarNotification sbn,
PeopleSpaceUtils.NotificationAction notificationAction,
- int appWidgetId) {
- PeopleSpaceTile storedTile = getTileForExistingWidget(appWidgetId);
- if (storedTile == null) {
- if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to");
- return;
- }
+ int appWidgetId, PeopleSpaceTile storedTile) {
if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) {
if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId);
storedTile = augmentTileFromNotification(mContext, storedTile, sbn);
- } else {
+ } else if (storedTile.getNotificationKey().equals(sbn.getKey())) {
if (DEBUG) {
Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId);
}
@@ -440,7 +620,8 @@
synchronized (mLock) {
if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getId());
PeopleTileKey key = new PeopleTileKey(tile);
- PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId);
+ PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
+ tile.getContactUri());
}
try {
if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
@@ -496,6 +677,7 @@
// Retrieve storage needed for widget deletion.
PeopleTileKey key;
Set<String> storedWidgetIdsForKey;
+ String contactUriString;
synchronized (mLock) {
SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId),
Context.MODE_PRIVATE);
@@ -509,9 +691,11 @@
}
storedWidgetIdsForKey = new HashSet<>(
mSharedPrefs.getStringSet(key.toString(), new HashSet<>()));
+ contactUriString = mSharedPrefs.getString(String.valueOf(widgetId), null);
}
synchronized (mLock) {
- PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId);
+ PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId,
+ contactUriString);
}
// If another tile with the conversation is still stored, we need to keep the listener.
if (DEBUG) Log.d(TAG, "Stored widget IDs: " + storedWidgetIdsForKey.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 05bc6e2..d54d3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -140,9 +140,10 @@
}
private void updateDetailText() {
- mDetailDoneButton.setText(R.string.quick_settings_done);
- final int resId =
- mDetailAdapter != null ? mDetailAdapter.getSettingsText() : Resources.ID_NULL;
+ int resId = mDetailAdapter != null ? mDetailAdapter.getDoneText() : Resources.ID_NULL;
+ mDetailDoneButton.setText(
+ (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_done);
+ resId = mDetailAdapter != null ? mDetailAdapter.getSettingsText() : Resources.ID_NULL;
mDetailSettingsButton.setText(
(resId != Resources.ID_NULL) ? resId : R.string.quick_settings_more_settings);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 5e13fd3..2c5dbcd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -283,31 +283,17 @@
return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
vpnName);
}
+ if (hasWorkProfile && isNetworkLoggingEnabled) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_network_activity);
+ }
if (isProfileOwnerOfOrganizationOwnedDevice) {
- if (isNetworkLoggingEnabled) {
- if (organizationName == null) {
- return mContext.getString(
- R.string.quick_settings_disclosure_management_monitoring);
- }
- return mContext.getString(
- R.string.quick_settings_disclosure_named_management_monitoring,
- organizationName);
- }
if (workProfileOrganizationName == null) {
return mContext.getString(R.string.quick_settings_disclosure_management);
}
return mContext.getString(R.string.quick_settings_disclosure_named_management,
workProfileOrganizationName);
}
- if (hasWorkProfile && isNetworkLoggingEnabled) {
- if (workProfileOrganizationName == null) {
- return mContext.getString(
- R.string.quick_settings_disclosure_managed_profile_monitoring);
- }
- return mContext.getString(
- R.string.quick_settings_disclosure_named_managed_profile_monitoring,
- workProfileOrganizationName);
- }
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 3e3451e..0a9c12f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -474,22 +474,21 @@
if (tiles.contains("internet") || tiles.contains("wifi") || tiles.contains("cell")) {
if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
if (!tiles.contains("internet")) {
- tiles.add("internet");
- }
- if (tiles.contains("wifi")) {
+ if (tiles.contains("wifi")) {
+ // Replace the WiFi with Internet, and remove the Cell
+ tiles.set(tiles.indexOf("wifi"), "internet");
+ tiles.remove("cell");
+ } else if (tiles.contains("cell")) {
+ // Replace the Cell with Internet
+ tiles.set(tiles.indexOf("cell"), "internet");
+ }
+ } else {
tiles.remove("wifi");
- }
- if (tiles.contains("cell")) {
tiles.remove("cell");
}
} else {
if (tiles.contains("internet")) {
- tiles.remove("internet");
- }
- if (!tiles.contains("wifi")) {
- tiles.add("wifi");
- }
- if (!tiles.contains("cell")) {
+ tiles.set(tiles.indexOf("internet"), "wifi");
tiles.add("cell");
}
}
@@ -513,6 +512,14 @@
&& GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
}
+ // TODO(b/174753536): Change the config file directly.
+ // Filter out unused tiles from the default QS config.
+ if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ tiles.remove("cell");
+ tiles.remove("wifi");
+ } else {
+ tiles.remove("internet");
+ }
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 32b41ec..d72f8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -115,34 +115,13 @@
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
// TODO(b/174753536): Move it into the config file.
- // Only do the below hacking when at least one of the below tiles exist
- // --InternetTile
- // --WiFiTile
- // --CellularTIle
- if (possibleTiles.contains("internet") || possibleTiles.contains("wifi")
- || possibleTiles.contains("cell")) {
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- if (!possibleTiles.contains("internet")) {
- possibleTiles.add("internet");
- }
- if (possibleTiles.contains("wifi")) {
- possibleTiles.remove("wifi");
- }
- if (possibleTiles.contains("cell")) {
- possibleTiles.remove("cell");
- }
- } else {
- if (possibleTiles.contains("internet")) {
- possibleTiles.remove("internet");
- }
- if (!possibleTiles.contains("wifi")) {
- possibleTiles.add("wifi");
- }
- if (!possibleTiles.contains("cell")) {
- possibleTiles.add("cell");
- }
- }
+ if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ possibleTiles.remove("cell");
+ possibleTiles.remove("wifi");
+ } else {
+ possibleTiles.remove("internet");
}
+
for (String spec : possibleTiles) {
// Only add current and stock tiles that can be created from QSFactoryImpl.
// Do not include CustomTile. Those will be created by `addPackageTiles`.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index fea521f..bdb3926 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -241,15 +241,7 @@
public void run() {
final float valFloat;
final boolean inVrMode = mIsVrModeEnabled;
- if (inVrMode) {
- valFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mDefaultBacklightForVr,
- UserHandle.USER_CURRENT);
- } else {
- valFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBacklight,
- UserHandle.USER_CURRENT);
- }
+ valFloat = mDisplayManager.getBrightness(mDisplayId);
// Value is passed as intbits, since this is what the message takes.
final int valueAsIntBits = Float.floatToIntBits(valFloat);
mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
@@ -364,14 +356,12 @@
metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR;
minBacklight = mMinimumBacklightForVr;
maxBacklight = mMaximumBacklightForVr;
- settingToChange = Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT;
} else {
metric = mAutomatic
? MetricsEvent.ACTION_BRIGHTNESS_AUTO
: MetricsEvent.ACTION_BRIGHTNESS;
minBacklight = PowerManager.BRIGHTNESS_MIN;
maxBacklight = PowerManager.BRIGHTNESS_MAX;
- settingToChange = Settings.System.SCREEN_BRIGHTNESS_FLOAT;
}
final float valFloat = MathUtils.min(convertGammaToLinearFloat(value,
minBacklight, maxBacklight),
@@ -386,8 +376,7 @@
if (!tracking) {
AsyncTask.execute(new Runnable() {
public void run() {
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- settingToChange, valFloat, UserHandle.USER_CURRENT);
+ mDisplayManager.setBrightness(mDisplayId, valFloat);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index 6f80317..05af08e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -25,10 +25,8 @@
import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
-import kotlin.math.max
-private const val RIPPLE_ANIMATION_DURATION: Long = 1500
-private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
+private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
/**
* Expanding ripple effect that shows when charging begins.
@@ -39,17 +37,18 @@
private val defaultColor: Int = 0xffffffff.toInt()
private val ripplePaint = Paint()
+ var radius: Float = 0.0f
+ set(value) { rippleShader.radius = value }
+ var origin: PointF = PointF()
+ set(value) { rippleShader.origin = value }
+ var duration: Long = 1500
+
init {
rippleShader.color = defaultColor
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader
- }
-
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
- rippleShader.origin = PointF(measuredWidth / 2f, measuredHeight.toFloat())
- rippleShader.radius = max(measuredWidth, measuredHeight).toFloat()
- super.onLayout(changed, left, top, right, bottom)
+ visibility = View.GONE
}
fun startRipple() {
@@ -57,7 +56,7 @@
return // Ignore if ripple effect is already playing
}
val animator = ValueAnimator.ofFloat(0f, 1f)
- animator.duration = RIPPLE_ANIMATION_DURATION
+ animator.duration = duration
animator.addUpdateListener { animator ->
val now = animator.currentPlayTime
val phase = now / 30000f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
index 5547c1e..d400205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
@@ -53,7 +53,7 @@
float s = 0.0;
for (float i = 0; i < 4; i += 1) {
float l = i * 0.25;
- float h = l + 0.025;
+ float h = l + 0.005;
float o = abs(sin(0.1 * PI * (t + i)));
s += threshold(n + o, l, h);
}
@@ -97,7 +97,7 @@
float fadeRipple = min(fadeIn, 1.-fadeOutRipple);
float rippleAlpha = softRing(p, in_origin, radius, 0.5)
* fadeRipple * in_color.a;
- vec4 ripple = in_color * max(circle, rippleAlpha) * 0.4;
+ vec4 ripple = in_color * max(circle, rippleAlpha) * 0.3;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}"""
private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index b567ad4..2900462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.res.Configuration
+import android.graphics.PointF
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroupOverlay
@@ -31,6 +32,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.io.PrintWriter
+import java.lang.Integer.max
import javax.inject.Inject
/***
@@ -46,7 +48,7 @@
private val context: Context,
private val keyguardStateController: KeyguardStateController
) {
- private var pluggedIn: Boolean? = null
+ private var charging: Boolean? = null
private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled
@VisibleForTesting
var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
@@ -55,16 +57,18 @@
val batteryStateChangeCallback = object : BatteryController.BatteryStateChangeCallback {
override fun onBatteryLevelChanged(
level: Int,
- nowPluggedIn: Boolean,
- charging: Boolean
+ pluggedIn: Boolean,
+ nowCharging: Boolean
) {
- if (!rippleEnabled) {
+ // Suppresses the ripple when it's disabled, or when the state change comes
+ // from wireless charging.
+ if (!rippleEnabled || batteryController.isWirelessCharging) {
return
}
- val wasPluggedIn = pluggedIn
- pluggedIn = nowPluggedIn
+ val wasCharging = charging
+ charging = nowCharging
// Only triggers when the keyguard is active and the device is just plugged in.
- if (wasPluggedIn == false && nowPluggedIn && keyguardStateController.isShowing) {
+ if (wasCharging == false && nowCharging && keyguardStateController.isShowing) {
rippleView.startRipple()
}
}
@@ -113,10 +117,13 @@
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
if (width != rippleView.width || height != rippleView.height) {
- rippleView.measure(
- View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
- rippleView.layout(0, 0, width, height)
+ rippleView.apply {
+ measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
+ layout(0, 0, width, height)
+ origin = PointF(width / 2f, height.toFloat())
+ radius = max(width, height).toFloat()
+ }
}
}
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 c83b60d..d581c4b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -184,7 +184,6 @@
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
@@ -2469,39 +2468,19 @@
protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel,
long animationDelay) {
- if (mDozing || mKeyguardManager.isKeyguardLocked()) {
- // on ambient or lockscreen, hide notification panel
- WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
- transmittingBatteryLevel, batteryLevel,
- new WirelessChargingAnimation.Callback() {
- @Override
- public void onAnimationStarting() {
- mNotificationShadeWindowController.setRequestTopUi(true, TAG);
- CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1);
- }
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ transmittingBatteryLevel, batteryLevel,
+ new WirelessChargingAnimation.Callback() {
+ @Override
+ public void onAnimationStarting() {
+ mNotificationShadeWindowController.setRequestTopUi(true, TAG);
+ }
- @Override
- public void onAnimationEnded() {
- CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView());
- mNotificationShadeWindowController.setRequestTopUi(false, TAG);
- }
- }, mDozing).show(animationDelay);
- } else {
- // workspace
- WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
- transmittingBatteryLevel, batteryLevel,
- new WirelessChargingAnimation.Callback() {
- @Override
- public void onAnimationStarting() {
- mNotificationShadeWindowController.setRequestTopUi(true, TAG);
- }
-
- @Override
- public void onAnimationEnded() {
- mNotificationShadeWindowController.setRequestTopUi(false, TAG);
- }
- }, false).show(animationDelay);
- }
+ @Override
+ public void onAnimationEnded() {
+ mNotificationShadeWindowController.setRequestTopUi(false, TAG);
+ }
+ }, false).show(animationDelay);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 4615877..fd37d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -321,6 +321,11 @@
}
@Override
+ public int getDoneText() {
+ return R.string.quick_settings_close_user_panel;
+ }
+
+ @Override
public boolean onDoneButtonClicked() {
if (mNotificationPanelViewController != null) {
mNotificationPanelViewController.animateCloseQs(true /* animateAway */);
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
index 3bc2632..95216c5 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
@@ -28,6 +28,12 @@
import javax.inject.Inject;
+/**
+ * Class for use by {@link TelephonyListenerManager} to centralize TelephonyManager Callbacks.
+ *
+ * There are more callback interfaces defined in {@link android.telephony.TelephonyCallback} that
+ * are not currently covered. Add them here if they ever become necessary.
+ */
class TelephonyCallback extends android.telephony.TelephonyCallback
implements ActiveDataSubscriptionIdListener, CallStateListener, ServiceStateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
index 4e1acca..3111930 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
@@ -47,7 +47,9 @@
private boolean mListening = false;
@Inject
- public TelephonyListenerManager(TelephonyManager telephonyManager, @Main Executor executor,
+ public TelephonyListenerManager(
+ TelephonyManager telephonyManager,
+ @Main Executor executor,
TelephonyCallback telephonyCallback) {
mTelephonyManager = telephonyManager;
mExecutor = executor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 7090e78..d91625e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -17,11 +17,14 @@
package com.android.systemui.people.widget;
import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.Notification.EXTRA_PEOPLE_LIST;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+import static android.content.PermissionChecker.PERMISSION_HARD_DENIED;
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
@@ -55,6 +58,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -86,6 +90,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -106,6 +111,8 @@
private static final int SECOND_WIDGET_ID_WITH_SHORTCUT = 3;
private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2;
private static final int WIDGET_ID_WITH_KEY_IN_OPTIONS = 4;
+ private static final int WIDGET_ID_WITH_SAME_URI = 5;
+ private static final int WIDGET_ID_WITH_DIFFERENT_URI = 6;
private static final String SHORTCUT_ID = "101";
private static final String OTHER_SHORTCUT_ID = "102";
private static final String NOTIFICATION_KEY = "0|com.android.systemui.tests|0|null|0";
@@ -123,10 +130,21 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID, "username", ICON, new Intent())
.setPackageName(TEST_PACKAGE_A)
- .setUserHandle(new UserHandle(1))
+ .setUserHandle(new UserHandle(0))
.setNotificationKey(NOTIFICATION_KEY + "1")
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
+ .setContactUri(URI)
+ .build();
+ private static final PeopleSpaceTile PERSON_TILE_WITH_SAME_URI =
+ new PeopleSpaceTile
+ // Different shortcut ID
+ .Builder(OTHER_SHORTCUT_ID, "username", ICON, new Intent())
+ // Different package name
+ .setPackageName(TEST_PACKAGE_B)
+ .setUserHandle(new UserHandle(0))
+ // Same contact uri.
+ .setContactUri(URI)
.build();
private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
SHORTCUT_ID).setLongLabel("name").build();
@@ -149,6 +167,8 @@
private LauncherApps mLauncherApps;
@Mock
private NotificationEntryManager mNotificationEntryManager;
+ @Mock
+ private PackageManager mPackageManager;
@Captor
private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -167,7 +187,7 @@
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
mManager = new PeopleSpaceWidgetManager(mContext);
mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager,
- mLauncherApps, mNotificationEntryManager);
+ mLauncherApps, mNotificationEntryManager, mPackageManager, true);
mManager.attach(mListenerService);
mProvider = new PeopleSpaceWidgetProvider();
mProvider.setPeopleSpaceWidgetManager(mManager);
@@ -177,16 +197,10 @@
mNoMan.addListener(serviceListener);
clearStorage();
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
-
- Bundle options = new Bundle();
- options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
- when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT)))
- .thenReturn(options);
+ addTileForWidget(PERSON_TILE, WIDGET_ID_WITH_SHORTCUT);
+ addTileForWidget(PERSON_TILE_WITH_SAME_URI, WIDGET_ID_WITH_SAME_URI);
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
.thenReturn(new Bundle());
- when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(
- getConversationWithShortcutId(SHORTCUT_ID));
}
@Test
@@ -477,7 +491,7 @@
addSecondWidgetForPersonTile();
PeopleSpaceUtils.removeSharedPreferencesStorageForTile(
- mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT);
+ mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT, EMPTY_STRING);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setSbn(createNotification(
SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false))
@@ -501,7 +515,6 @@
throws Exception {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setSbn(createNotification(
@@ -527,7 +540,6 @@
throws Exception {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setSbn(createNotification(
@@ -548,10 +560,267 @@
}
@Test
+ public void testUpdateMissedCallNotificationWithContentPostedIfMatchingUriTile()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ mBundleArgumentCaptor.capture());
+ Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testRemoveMissedCallNotificationWithContentPostedIfMatchingUriTile()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+ NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn.cloneLight(), 0);
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = mBundleArgumentCaptor.getValue();
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(null);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(null);
+ verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(2))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ mBundleArgumentCaptor.capture());
+ Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(null);
+ assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(null);
+ verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotRemoveMissedCallIfMatchingUriTileMissingReadContactsPermissionWhenPosted()
+ throws Exception {
+ when(mPackageManager.checkPermission(any(),
+ eq(PERSON_TILE_WITH_SAME_URI.getPackageName()))).thenReturn(
+ PERMISSION_HARD_DENIED);
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+ // We should only try to remove the notification if the Missed Call was added when posted.
+ NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn.cloneLight(), 0);
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = mBundleArgumentCaptor.getValue();
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(null);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(null);
+ verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testUpdateMissedCallNotificationWithContentPostedIfMatchingUriTileFromSender()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ Notification notificationWithPersonOnlyInSender =
+ createMessagingStyleNotificationWithoutExtras(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */
+ true).build();
+ StatusBarNotification sbn = new SbnBuilder()
+ .setNotification(notificationWithPersonOnlyInSender)
+ .setPkg(TEST_PACKAGE_A)
+ .setUid(0)
+ .setUser(new UserHandle(0))
+ .build();
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(sbn)
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ mBundleArgumentCaptor.capture());
+ Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotUpdateMissedCallNotificationWithContentPostedIfNoPersonsAttached()
+ throws Exception {
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ // Notification posted without any Person attached.
+ Notification notificationWithoutPersonObject =
+ createMessagingStyleNotificationWithoutExtras(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */
+ true).setStyle(new Notification.MessagingStyle("sender")
+ .addMessage(
+ new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10,
+ "sender"))
+ ).build();
+ StatusBarNotification sbn = new SbnBuilder()
+ .setNotification(notificationWithoutPersonObject)
+ .setPkg(TEST_PACKAGE_A)
+ .setUid(0)
+ .setUser(new UserHandle(0))
+ .build();
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(sbn)
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ // Do not update since notification doesn't include a Person reference.
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotUpdateMissedCallNotificationWithContentPostedIfNotMatchingUriTile()
+ throws Exception {
+ clearStorage();
+ addTileForWidget(PERSON_TILE, WIDGET_ID_WITH_SHORTCUT);
+ addTileForWidget(PERSON_TILE_WITH_SAME_URI.toBuilder().setContactUri(
+ Uri.parse("different_uri")).build(), WIDGET_ID_WITH_DIFFERENT_URI);
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_DIFFERENT_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ // Do not update since missing permission to read contacts.
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_DIFFERENT_URI),
+ any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_DIFFERENT_URI),
+ any());
+ }
+
+ @Test
+ public void testDoNotUpdateMissedCallIfMatchingUriTileMissingReadContactsPermission()
+ throws Exception {
+ when(mPackageManager.checkPermission(any(),
+ eq(PERSON_TILE_WITH_SAME_URI.getPackageName()))).thenReturn(
+ PERMISSION_HARD_DENIED);
+ int[] widgetIdsArray =
+ {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setSbn(createNotification(
+ SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true))
+ .setId(1));
+ mClock.advanceTime(MIN_LINGER_DURATION);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
+ PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+ assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
+ NOTIFICATION_CONTENT);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ // Do not update since missing permission to read contacts.
+ verify(mAppWidgetManager, times(0))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
+ any());
+ }
+
+ @Test
public void testUpdateNotificationRemovedIfExistingTile() throws Exception {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
StatusBarNotification sbn = createNotification(
SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false);
@@ -574,7 +843,8 @@
}
@Test
- public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners() {
+ public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners()
+ throws Exception {
addSecondWidgetForPersonTile();
mProvider.onUpdate(mContext, mAppWidgetManager,
new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT});
@@ -746,19 +1016,26 @@
* Adds another widget for {@code PERSON_TILE} with widget ID: {@code
* SECOND_WIDGET_ID_WITH_SHORTCUT}.
*/
- private void addSecondWidgetForPersonTile() {
- Bundle options = new Bundle();
- options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
- when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT)))
- .thenReturn(options);
+ private void addSecondWidgetForPersonTile() throws Exception {
// Set the same Person associated on another People Tile widget ID.
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
- setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT);
+ addTileForWidget(PERSON_TILE, SECOND_WIDGET_ID_WITH_SHORTCUT);
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT,
SECOND_WIDGET_ID_WITH_SHORTCUT};
when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
}
+ private void addTileForWidget(PeopleSpaceTile tile, int widgetId) throws Exception {
+ setStorageForTile(tile.getId(), tile.getPackageName(), widgetId, tile.getContactUri());
+ Bundle options = new Bundle();
+ options.putParcelable(OPTIONS_PEOPLE_TILE, tile);
+ when(mAppWidgetManager.getAppWidgetOptions(eq(widgetId)))
+ .thenReturn(options);
+ when(mIPeopleManager.getConversation(tile.getPackageName(), 0, tile.getId())).thenReturn(
+ getConversationWithShortcutId(tile.getId()));
+ when(mPackageManager.checkPermission(any(), eq(tile.getPackageName()))).thenReturn(
+ PERMISSION_GRANTED);
+ }
+
/**
* Returns a single conversation associated with {@code shortcutId}.
*/
@@ -772,7 +1049,7 @@
private ConversationChannel getConversationWithShortcutId(String shortcutId,
List<ConversationStatus> statuses) throws Exception {
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
- "name").build();
+ "name").setPerson(PERSON).build();
ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
0L, false, false, statuses);
return convo;
@@ -780,6 +1057,30 @@
private Notification createMessagingStyleNotification(String shortcutId,
boolean isMessagingStyle, boolean isMissedCall) {
+ Bundle extras = new Bundle();
+ ArrayList<Person> person = new ArrayList<Person>();
+ person.add(PERSON);
+ extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, person);
+ Notification.Builder builder = new Notification.Builder(mContext)
+ .setContentTitle("TEST_TITLE")
+ .setContentText("TEST_TEXT")
+ .setExtras(extras)
+ .setShortcutId(shortcutId);
+ if (isMessagingStyle) {
+ builder.setStyle(new Notification.MessagingStyle(PERSON)
+ .addMessage(
+ new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10,
+ PERSON))
+ );
+ }
+ if (isMissedCall) {
+ builder.setCategory(CATEGORY_MISSED_CALL);
+ }
+ return builder.build();
+ }
+
+ private Notification.Builder createMessagingStyleNotificationWithoutExtras(String shortcutId,
+ boolean isMessagingStyle, boolean isMissedCall) {
Notification.Builder builder = new Notification.Builder(mContext)
.setContentTitle("TEST_TITLE")
.setContentText("TEST_TEXT")
@@ -794,9 +1095,10 @@
if (isMissedCall) {
builder.setCategory(CATEGORY_MISSED_CALL);
}
- return builder.build();
+ return builder;
}
+
private StatusBarNotification createNotification(String shortcutId,
boolean isMessagingStyle, boolean isMissedCall) {
Notification notification = createMessagingStyleNotification(
@@ -804,6 +1106,8 @@
return new SbnBuilder()
.setNotification(notification)
.setPkg(TEST_PACKAGE_A)
+ .setUid(0)
+ .setUser(new UserHandle(0))
.build();
}
@@ -824,11 +1128,16 @@
String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS),
Context.MODE_PRIVATE);
widgetSp4.edit().clear().commit();
+ SharedPreferences widgetSp5 = mContext.getSharedPreferences(
+ String.valueOf(WIDGET_ID_WITH_SAME_URI),
+ Context.MODE_PRIVATE);
+ widgetSp5.edit().clear().commit();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
sp.edit().clear().commit();
}
- private void setStorageForTile(String shortcutId, String packageName, int widgetId) {
+ private void setStorageForTile(String shortcutId, String packageName, int widgetId,
+ Uri contactUri) {
SharedPreferences widgetSp = mContext.getSharedPreferences(
String.valueOf(widgetId),
Context.MODE_PRIVATE);
@@ -840,11 +1149,17 @@
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = sp.edit();
- editor.putString(String.valueOf(widgetId), shortcutId);
+ editor.putString(String.valueOf(widgetId), contactUri.toString());
+
String key = new PeopleTileKey(shortcutId, 0, packageName).toString();
Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
storedWidgetIds.add(String.valueOf(widgetId));
editor.putStringSet(key, storedWidgetIds);
+
+ Set<String> storedWidgetIdsByUri = new HashSet<>(
+ sp.getStringSet(contactUri.toString(), new HashSet<>()));
+ storedWidgetIdsByUri.add(String.valueOf(widgetId));
+ editor.putStringSet(contactUri.toString(), storedWidgetIdsByUri);
editor.apply();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
index 3701b91..9ce7241 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
@@ -93,8 +93,8 @@
captor.value.onBatteryLevelChanged(
unusedBatteryLevel,
- true /* plugged in */,
- false /* charging */)
+ false /* plugged in */,
+ true /* charging */)
verify(rippleView).startRipple()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
index 463b336..ac15903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
@@ -27,7 +27,6 @@
import com.android.systemui.SysuiTestCase;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,7 +35,7 @@
public class TelephonyCallbackTest extends SysuiTestCase {
private TelephonyCallback mTelephonyCallback = new TelephonyCallback();
-
+
@Test
public void testAddListener_ActiveDataSubscriptionIdListener() {
assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index 443a9bc..f971a09 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -28,6 +28,17 @@
]]> appears at the top of your screen when VPN is active.
</string>
+ <!-- TV specific dialog message to warn about the risk of using a VPN application. [CHAR LIMIT=NONE] -->
+ <string name="warning" product="tv">
+ <xliff:g id="app">%s</xliff:g> wants to set up a VPN connection
+ that allows it to monitor network traffic. Only accept if you trust the source.
+ <![CDATA[
+ <br />
+ <br />
+ <img src="vpn_icon" />
+ ]]> appears on your screen when VPN is active.
+ </string>
+
<!-- Dialog title for built-in VPN. [CHAR LIMIT=40] -->
<string name="legacy_title">VPN is connected</string>
<!-- Label for the name of the current VPN session. [CHAR LIMIT=20] -->
diff --git a/services/Android.bp b/services/Android.bp
index f6bb157..311e8735 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -153,7 +153,7 @@
" --hide DeprecationMismatch" +
" --hide HiddenTypedefConstant",
visibility: ["//visibility:private"],
- filter_packages: ["com.android."]
+ filter_packages: ["com.android."],
}
droidstubs {
@@ -168,7 +168,7 @@
last_released: {
api_file: ":android.api.system-server.latest",
removed_api_file: ":removed.api.system-server.latest",
- baseline_file: ":android-incompatibilities.api.system-server.latest"
+ baseline_file: ":android-incompatibilities.api.system-server.latest",
},
api_lint: {
enabled: true,
@@ -178,18 +178,24 @@
},
dists: [
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/system-server/api",
dest: "android.txt",
- tag: ".api.txt"
+ tag: ".api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/system-server/api",
dest: "removed.txt",
tag: ".removed-api.txt",
},
- ]
+ ],
}
java_library {
@@ -223,16 +229,22 @@
},
dists: [
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt"
+ tag: ".api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
- ]
-}
\ No newline at end of file
+ ],
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
index d2c1bc1..1fdd908 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
@@ -108,7 +108,7 @@
SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION);
final String message = mContext.getString(R.string.window_magnification_prompt_content);
- notificationBuilder.setSmallIcon(R.drawable.ic_settings_24dp)
+ notificationBuilder.setSmallIcon(R.drawable.ic_accessibility_24dp)
.setContentTitle(mContext.getString(R.string.window_magnification_prompt_title))
.setContentText(message)
.setLargeIcon(Icon.createWithResource(mContext,
@@ -118,7 +118,7 @@
.setStyle(new Notification.BigTextStyle().bigText(message))
.setDeleteIntent(createPendingIntent(ACTION_DISMISS))
.setContentIntent(createPendingIntent(ACTION_TURN_ON_IN_SETTINGS))
- .setActions(buildTurnOnAction(), buildDismissAction());
+ .setActions(buildTurnOnAction());
mNotificationManager.notify(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE,
notificationBuilder.build());
registerReceiverIfNeeded();
@@ -145,11 +145,6 @@
createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)).build();
}
- private Notification.Action buildDismissAction() {
- return new Notification.Action.Builder(null, mContext.getString(R.string.dismiss_action),
- createPendingIntent(ACTION_DISMISS)).build();
- }
-
private PendingIntent createPendingIntent(String action) {
final Intent intent = new Intent(action);
intent.setPackage(mContext.getPackageName());
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b160d78..483f67a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -639,6 +639,16 @@
}));
}
+ @Override
+ public boolean createAssociation(String packageName, String macAddress, int userId) {
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation");
+
+ addAssociation(new Association(
+ userId, macAddress, packageName, null, false, System.currentTimeMillis()));
+ return true;
+ }
+
private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
checkCallerIsSystemOr(callingPackage);
int userId = getCallingUserId();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 8f56842..8bb9ce9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3716,6 +3716,7 @@
mDnsManager.removeNetwork(nai.network);
}
mNetIdManager.releaseNetId(nai.network.getNetId());
+ nai.onNetworkDisconnected();
}
private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
@@ -8126,6 +8127,7 @@
updateCapabilitiesForNetwork(networkAgent);
}
networkAgent.created = true;
+ networkAgent.onNetworkCreated();
}
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 411fc67..3ea4458 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -366,11 +366,13 @@
|| events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED);
}
- private boolean isPhoneStatePermissionRequired(Set<Integer> events) {
+ private boolean isPhoneStatePermissionRequired(Set<Integer> events, int targetSdk) {
return events.contains(TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)
|| events.contains(TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)
|| events.contains(TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)
- || events.contains(TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
+ || events.contains(TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)
+ || (targetSdk <= android.os.Build.VERSION_CODES.R ? events.contains(
+ TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED) : false);
}
private boolean isPrecisePhoneStatePermissionRequired(Set<Integer> events) {
@@ -882,12 +884,12 @@
remove(callback.asBinder());
return;
}
-
+ int callerTargetSdk = TelephonyPermissions.getTargetSdk(mContext, callingPackage);
// Checks permission and throws SecurityException for disallowed operations. For pre-M
// apps whose runtime permission has been revoked, we return immediately to skip sending
// events to the app without crashing it.
if (!checkListenerPermission(events, subId, callingPackage, callingFeatureId,
- "listen")) {
+ "listen", callerTargetSdk)) {
return;
}
@@ -920,7 +922,7 @@
}
r.phoneId = phoneId;
r.eventList = events;
- r.targetSdk = TelephonyPermissions.getTargetSdk(mContext, callingPackage);
+ r.targetSdk = callerTargetSdk;
if (DBG) {
log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
@@ -1180,7 +1182,9 @@
TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)) {
try {
r.callback.onPhysicalChannelConfigChanged(
- mPhysicalChannelConfigs);
+ shouldSanitizeLocationForPhysicalChannelConfig(r)
+ ? getLocationSanitizedConfigs(mPhysicalChannelConfigs)
+ : mPhysicalChannelConfigs);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2423,8 +2427,10 @@
return;
}
+ List<PhysicalChannelConfig> sanitizedConfigs = getLocationSanitizedConfigs(configs);
if (VDBG) {
- log("notifyPhysicalChannelConfig: subId=" + subId + " configs=" + configs);
+ log("notifyPhysicalChannelConfig: subId=" + subId + " configs=" + configs
+ + " sanitizedConfigs=" + sanitizedConfigs);
}
synchronized (mRecords) {
@@ -2437,11 +2443,14 @@
&& idMatch(r.subId, subId, phoneId)) {
try {
if (DBG_LOC) {
- log("notifyPhysicalChannelConfig: "
- + "mPhysicalChannelConfigs="
- + configs + " r=" + r);
+ log("notifyPhysicalChannelConfig: mPhysicalChannelConfigs="
+ + (shouldSanitizeLocationForPhysicalChannelConfig(r)
+ ? sanitizedConfigs : configs)
+ + " r=" + r);
}
- r.callback.onPhysicalChannelConfigChanged(configs);
+ r.callback.onPhysicalChannelConfigChanged(
+ shouldSanitizeLocationForPhysicalChannelConfig(r)
+ ? sanitizedConfigs : configs);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2452,6 +2461,25 @@
}
}
+ private static boolean shouldSanitizeLocationForPhysicalChannelConfig(Record record) {
+ // Always redact location info from PhysicalChannelConfig if the registrant is from neither
+ // PHONE nor SYSTEM process. There is no user case that the registrant needs the location
+ // info (e.g. physicalCellId). This also remove the need for the location permissions check.
+ return record.callerUid != Process.PHONE_UID && record.callerUid != Process.SYSTEM_UID;
+ }
+
+ /**
+ * Return a copy of the PhysicalChannelConfig list but with location info removed.
+ */
+ private static List<PhysicalChannelConfig> getLocationSanitizedConfigs(
+ List<PhysicalChannelConfig> configs) {
+ List<PhysicalChannelConfig> sanitizedConfigs = new ArrayList<>(configs.size());
+ for (PhysicalChannelConfig config : configs) {
+ sanitizedConfigs.add(config.createLocationInfoSanitizedCopy());
+ }
+ return sanitizedConfigs;
+ }
+
/**
* Notify that the data enabled has changed.
*
@@ -2876,7 +2904,7 @@
}
private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage,
- @Nullable String callingFeatureId, String message) {
+ @Nullable String callingFeatureId, String message, int targetSdk) {
LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
new LocationAccessPolicy.LocationPermissionQuery.Builder()
.setCallingPackage(callingPackage)
@@ -2912,7 +2940,7 @@
}
}
- if (isPhoneStatePermissionRequired(events)) {
+ if (isPhoneStatePermissionRequired(events, targetSdk)) {
if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
mContext, subId, callingPackage, callingFeatureId, message)) {
isPermissionCheckSuccessful = false;
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index cfa110e..cd2d6d4 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -29,7 +29,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
@@ -158,6 +161,7 @@
@NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
@NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
@NonNull private final VcnContext mVcnContext;
+ @NonNull private final BroadcastReceiver mPkgChangeReceiver;
/** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */
@Nullable private LocationPermissionChecker mLocationPermissionChecker;
@@ -203,6 +207,29 @@
mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider);
+ mPkgChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+ || Intent.ACTION_PACKAGE_REPLACED.equals(action)
+ || Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ mTelephonySubscriptionTracker.handleSubscriptionsChanged();
+ } else {
+ Log.wtf(TAG, "received unexpected intent: " + action);
+ }
+ }
+ };
+
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiver(
+ mPkgChangeReceiver, intentFilter, null /* broadcastPermission */, mHandler);
+
// Run on handler to ensure I/O does not block system server startup
mHandler.post(() -> {
PersistableBundle configBundle = null;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 98931a5..c6405e0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -18,7 +18,6 @@
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;
@@ -49,6 +48,7 @@
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerWhitelistManager.getReasonCodeFromProcState;
import static android.os.PowerWhitelistManager.reasonCodeToString;
+import static android.os.Process.INVALID_UID;
import static android.os.Process.NFC_UID;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
@@ -660,7 +660,7 @@
}
ServiceRecord r = res.record;
- setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r,
+ setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
allowBackgroundActivityStarts);
if (!mAm.mUserController.exists(r.userId)) {
@@ -693,19 +693,7 @@
+ 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)) {
+ if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
throw new ForegroundServiceStartNotAllowedException(msg);
}
return null;
@@ -839,7 +827,7 @@
boolean addToStarting = false;
if (!callerFg && !fgRequired && r.app == null
&& mAm.mUserController.hasStartedUserState(r.userId)) {
- ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
+ ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid);
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
@@ -1808,7 +1796,7 @@
final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime;
if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, false);
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,false);
final String temp = "startForegroundDelayMs:" + delayMs;
if (r.mInfoAllowStartForeground != null) {
r.mInfoAllowStartForeground += "; " + temp;
@@ -1825,7 +1813,7 @@
r.mLastSetFgsRestrictionTime;
if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, false);
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,false);
}
}
logFgsBackgroundStart(r);
@@ -2579,7 +2567,8 @@
return 0;
}
}
- setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, false);
+ setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
+ false);
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
@@ -3451,7 +3440,7 @@
ProcessRecord app;
if (!isolated) {
- app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
+ app = mAm.getProcessRecordLocked(procName, r.appInfo.uid);
if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
+ " app=" + app);
if (app != null) {
@@ -3499,7 +3488,7 @@
// 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) {
+ hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated)) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
@@ -5469,7 +5458,7 @@
* @return true if allow, false otherwise.
*/
private void setFgsRestrictionLocked(String callingPackage,
- int callingPid, int callingUid, Intent intent, ServiceRecord r,
+ int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
boolean allowBackgroundActivityStarts) {
r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
// Check DeviceConfig flag.
@@ -5487,7 +5476,7 @@
if (r.mAllowStartForeground == REASON_DENIED) {
r.mAllowStartForeground = shouldAllowFgsStartForegroundLocked(allowWhileInUse,
callingPackage, callingPid, callingUid, intent, r,
- allowBackgroundActivityStarts);
+ userId);
}
}
}
@@ -5630,13 +5619,20 @@
*/
private @ReasonCode int shouldAllowFgsStartForegroundLocked(
@ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
- int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) {
+ int callingUid, Intent intent, ServiceRecord r, int userId) {
FgsStartTempAllowList.TempFgsAllowListEntry tempAllowListReason =
r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
int ret = shouldAllowFgsStartForegroundLocked(allowWhileInUse, callingPid, callingUid,
callingPackage, r);
final int uidState = mAm.getUidStateLocked(callingUid);
+ int callerTargetSdkVersion = INVALID_UID;
+ try {
+ ApplicationInfo ai = mAm.mContext.getPackageManager().getApplicationInfoAsUser(
+ callingPackage, PackageManager.MATCH_KNOWN_PACKAGES, userId);
+ callerTargetSdkVersion = ai.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ }
final String debugInfo =
"[callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
@@ -5652,6 +5648,7 @@
+ ",callingUid:" + tempAllowListReason.mCallingUid))
+ ">"
+ "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+ + "; callerTargetSdkVersion:" + callerTargetSdkVersion
+ "; startForegroundCount:" + r.mStartForegroundCount
+ "]";
if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
@@ -5823,7 +5820,12 @@
private boolean isBgFgsRestrictionEnabled(ServiceRecord r) {
return mAm.mConstants.mFlagFgsStartRestrictionEnabled
- && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid);
+ // Checking service's targetSdkVersion.
+ && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid)
+ && (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk
+ // Checking callingUid's targetSdkVersion.
+ || CompatChanges.isChangeEnabled(
+ FGS_BG_START_RESTRICTION_CHANGE_ID, r.mRecentCallingUid));
}
private void logFgsBackgroundStart(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index f7abf6a..e16f4c9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -172,6 +172,13 @@
"default_fgs_starts_restriction_enabled";
/**
+ * Default value for mFgsStartRestrictionCheckCallerTargetSdk if not explicitly set in
+ * Settings.Global.
+ */
+ private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK =
+ "default_fgs_starts_restriction_check_caller_target_sdk";
+
+ /**
* Whether FGS notification display is deferred following the transition into
* the foreground state. Default behavior is {@code true} unless overridden.
*/
@@ -371,6 +378,13 @@
// at all.
volatile boolean mFlagFgsStartRestrictionEnabled = true;
+ /**
+ * Indicates whether the foreground service background start restriction is enabled for
+ * caller app that is targeting S+.
+ * This is in addition to check of {@link #mFlagFgsStartRestrictionEnabled} flag.
+ */
+ volatile boolean mFgsStartRestrictionCheckCallerTargetSdk = true;
+
// Whether we defer FGS notifications a few seconds following their transition to
// the foreground state. Applies only to S+ apps; enabled by default.
volatile boolean mFlagFgsNotificationDeferralEnabled = true;
@@ -554,6 +568,9 @@
case KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED:
updateFgsStartsRestriction();
break;
+ case KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK:
+ updateFgsStartsRestrictionCheckCallerTargetSdk();
+ break;
case KEY_DEFERRED_FGS_NOTIFICATIONS_ENABLED:
updateFgsNotificationDeferralEnable();
break;
@@ -829,6 +846,13 @@
/*defaultValue*/ true);
}
+ private void updateFgsStartsRestrictionCheckCallerTargetSdk() {
+ mFgsStartRestrictionCheckCallerTargetSdk = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK,
+ /*defaultValue*/ true);
+ }
+
private void updateFgsNotificationDeferralEnable() {
mFlagFgsNotificationDeferralEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1090,6 +1114,14 @@
pw.println(mFgToBgFgsGraceDuration);
pw.print(" "); pw.print(KEY_FGS_START_FOREGROUND_TIMEOUT); pw.print("=");
pw.println(mFgsStartForegroundTimeoutMs);
+ pw.print(" "); pw.print(KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED); pw.print("=");
+ pw.println(mFlagBackgroundActivityStartsEnabled);
+ pw.print(" "); pw.print(KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED);
+ pw.print("="); pw.println(mFlagBackgroundFgsStartRestrictionEnabled);
+ pw.print(" "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED); pw.print("=");
+ pw.println(mFlagFgsStartRestrictionEnabled);
+ pw.print(" "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK);
+ pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 321e3b1..2bceb20 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2604,8 +2604,8 @@
}
@GuardedBy("this")
- final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) {
- return mProcessList.getProcessRecordLocked(processName, uid, keepIfLarge);
+ final ProcessRecord getProcessRecordLocked(String processName, int uid) {
+ return mProcessList.getProcessRecordLocked(processName, uid);
}
@GuardedBy(anyOf = {"this", "mProcLock"})
@@ -2639,8 +2639,7 @@
false /* knownToBeDead */, 0 /* intentFlags */,
sNullHostingRecord /* hostingRecord */, ZYGOTE_POLICY_FLAG_EMPTY,
true /* allowWhileBooting */, true /* isolated */,
- uid, true /* keepIfLarge */, abiOverride, entryPoint, entryPointArgs,
- crashHandler);
+ uid, abiOverride, entryPoint, entryPointArgs, crashHandler);
return proc != null;
}
}
@@ -2649,10 +2648,10 @@
final ProcessRecord startProcessLocked(String processName,
ApplicationInfo info, boolean knownToBeDead, int intentFlags,
HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting,
- boolean isolated, boolean keepIfLarge) {
+ boolean isolated) {
return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
- keepIfLarge, null /* ABI override */, null /* entryPoint */,
+ null /* ABI override */, null /* entryPoint */,
null /* entryPointArgs */, null /* crashHandler */);
}
@@ -3905,7 +3904,7 @@
// Only the system server can kill an application
if (callerUid == SYSTEM_UID) {
synchronized (this) {
- ProcessRecord app = getProcessRecordLocked(processName, uid, true);
+ ProcessRecord app = getProcessRecordLocked(processName, uid);
IApplicationThread thread;
if (app != null && (thread = app.getThread()) != null) {
try {
@@ -6101,7 +6100,7 @@
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
- info.uid, true);
+ info.uid);
} else {
app = null;
}
@@ -11933,7 +11932,7 @@
ProcessRecord proc = startProcessLocked(app.processName, app,
false, 0,
new HostingRecord("backup", hostingName),
- ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false, false);
+ ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false);
if (proc == null) {
Slog.e(TAG, "Unable to start backup agent process " + r);
return false;
@@ -13643,7 +13642,7 @@
ProcessRecord app;
synchronized (mProcLock) {
if (noRestart) {
- app = getProcessRecordLocked(ai.processName, ai.uid, true);
+ app = getProcessRecordLocked(ai.processName, ai.uid);
} else {
// Instrumentation can kill and relaunch even persistent processes
forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false,
@@ -13686,7 +13685,7 @@
ApplicationInfo targetInfo) {
ProcessRecord pr;
synchronized (this) {
- pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid, true);
+ pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid);
}
try {
@@ -15433,8 +15432,7 @@
@Override
public void killProcess(String processName, int uid, String reason) {
synchronized (ActivityManagerService.this) {
- final ProcessRecord proc = getProcessRecordLocked(processName, uid,
- true /* keepIfLarge */);
+ final ProcessRecord proc = getProcessRecordLocked(processName, uid);
if (proc != null) {
mProcessList.removeProcessLocked(proc, false /* callerWillRestart */,
true /* allowRestart */, ApplicationExitInfo.REASON_OTHER, reason);
@@ -15841,7 +15839,7 @@
startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
new HostingRecord(hostingType, hostingName, isTop),
ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,
- false /* isolated */, true /* keepIfLarge */);
+ false /* isolated */);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index e74c936..60e8d54 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -487,8 +487,10 @@
Slog.wtf(TAG, "Error updating external stats: ", e);
}
- synchronized (BatteryExternalStatsWorker.this) {
- mLastCollectionTimeStamp = SystemClock.elapsedRealtime();
+ if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
+ synchronized (BatteryExternalStatsWorker.this) {
+ mLastCollectionTimeStamp = SystemClock.elapsedRealtime();
+ }
}
}
};
@@ -658,6 +660,11 @@
mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState,
elapsedRealtime);
}
+
+ final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC;
+ if (gnssChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) {
+ mStats.updateGnssMeasuredEnergyStatsLocked(displayChargeUC, elapsedRealtime);
+ }
}
// Inform mStats about each applicable custom energy bucket.
if (measuredEnergyDeltas != null
@@ -698,7 +705,10 @@
}
if (modemInfo != null) {
- mStats.noteModemControllerActivity(modemInfo, elapsedRealtime, uptime);
+ final long mobileRadioChargeUC = measuredEnergyDeltas != null
+ ? measuredEnergyDeltas.mobileRadioChargeUC : MeasuredEnergySnapshot.UNAVAILABLE;
+ mStats.noteModemControllerActivity(modemInfo, mobileRadioChargeUC, elapsedRealtime,
+ uptime);
}
if (updateFlags == UPDATE_ALL) {
@@ -830,6 +840,12 @@
case EnergyConsumerType.CPU_CLUSTER:
buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
break;
+ case EnergyConsumerType.GNSS:
+ buckets[MeasuredEnergyStats.POWER_BUCKET_GNSS] = true;
+ break;
+ case EnergyConsumerType.MOBILE_RADIO:
+ buckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO] = true;
+ break;
case EnergyConsumerType.DISPLAY:
buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true;
buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE] = true;
@@ -883,6 +899,9 @@
if ((flags & UPDATE_DISPLAY) != 0) {
addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
}
+ if ((flags & UPDATE_RADIO) != 0) {
+ addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.MOBILE_RADIO);
+ }
if ((flags & UPDATE_WIFI) != 0) {
addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.WIFI);
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c3f97ad..5937a18 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -674,6 +674,13 @@
public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
mContext.enforceCallingPermission(
android.Manifest.permission.BATTERY_STATS, null);
+ awaitCompletion();
+
+ if (mBatteryUsageStatsProvider.shouldUpdateStats(queries,
+ mWorker.getLastCollectionTimeStamp())) {
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
+ }
+
return mBatteryUsageStatsProvider.getBatteryUsageStats(queries);
}
@@ -1966,7 +1973,8 @@
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
- mStats.noteModemControllerActivity(info, elapsedRealtime, uptime);
+ mStats.noteModemControllerActivity(info, POWER_DATA_UNAVAILABLE, elapsedRealtime,
+ uptime);
});
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e79f096..9ecae42 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1544,7 +1544,7 @@
}
String targetProcess = info.activityInfo.processName;
ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
- info.activityInfo.applicationInfo.uid, false);
+ info.activityInfo.applicationInfo.uid);
if (!skip) {
final int allowed = mService.getAppStartModeLOSP(
@@ -1678,7 +1678,7 @@
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);
+ (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
if (r.curApp == null) {
// Ah, this recipient is unavailable. Finish it if necessary,
// and mark the broadcast record as ready for the next.
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 2c8794d..ee4526b 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -436,7 +436,7 @@
// Use existing process if already started
checkTime(startTime, "getContentProviderImpl: looking for process record");
ProcessRecord proc = mService.getProcessRecordLocked(
- cpi.processName, cpr.appInfo.uid, false);
+ cpi.processName, cpr.appInfo.uid);
IApplicationThread thread;
if (proc != null && (thread = proc.getThread()) != null
&& !proc.isKilled()) {
@@ -459,7 +459,7 @@
new HostingRecord("content provider",
new ComponentName(
cpi.applicationInfo.packageName, cpi.name)),
- Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false, false);
+ Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false);
checkTime(startTime, "getContentProviderImpl: after start process");
if (proc == null) {
Slog.w(TAG, "Unable to launch app "
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index d789bbb..f9296a0 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -118,6 +118,12 @@
/** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */
public long displayChargeUC = UNAVAILABLE;
+ /** The chargeUC for {@link EnergyConsumerType#GNSS}. */
+ public long gnssChargeUC = UNAVAILABLE;
+
+ /** The chargeUC for {@link EnergyConsumerType#MOBILE_RADIO}. */
+ public long mobileRadioChargeUC = UNAVAILABLE;
+
/** The chargeUC for {@link EnergyConsumerType#WIFI}. */
public long wifiChargeUC = UNAVAILABLE;
@@ -217,6 +223,14 @@
output.displayChargeUC = deltaChargeUC;
break;
+ case EnergyConsumerType.GNSS:
+ output.gnssChargeUC = deltaChargeUC;
+ break;
+
+ case EnergyConsumerType.MOBILE_RADIO:
+ output.mobileRadioChargeUC = deltaChargeUC;
+ break;
+
case EnergyConsumerType.WIFI:
output.wifiChargeUC = deltaChargeUC;
break;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 51bcde8..2cde423 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -36,7 +36,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -54,7 +53,6 @@
import static com.android.server.am.ActivityManagerService.TAG_NETWORK;
import static com.android.server.am.ActivityManagerService.TAG_PROCESSES;
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;
@@ -1501,8 +1499,7 @@
}
@GuardedBy("mService")
- final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean
- keepIfLarge) {
+ ProcessRecord getProcessRecordLocked(String processName, int uid) {
if (uid == SYSTEM_UID) {
// The system gets to run in any process. If there are multiple
// processes with the same uid, just pick the first (this
@@ -1519,41 +1516,7 @@
return procs.valueAt(i);
}
}
- ProcessRecord proc = mProcessNames.get(processName, uid);
- if (false && proc != null && !keepIfLarge
- && proc.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY
- && proc.mProfile.getLastCachedPss() >= 4000) {
- // Turn this condition on to cause killing to happen regularly, for testing.
- synchronized (mService.mAppProfiler.mProfilerLock) {
- proc.mProfile.reportCachedKill();
- }
- 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.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
- final long lastCachedPss;
- boolean doKilling = false;
- synchronized (mService.mAppProfiler.mProfilerLock) {
- lastCachedPss = proc.mProfile.getLastCachedPss();
- if (lastCachedPss >= getCachedRestoreThresholdKb()) {
- proc.mProfile.reportCachedKill();
- doKilling = true;
- }
- }
- if (DEBUG_PSS) {
- Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + lastCachedPss);
- }
- if (doKilling) {
- proc.killLocked(Long.toString(lastCachedPss) + "k from cached",
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_LARGE_CACHED,
- true);
- }
- }
- return proc;
+ return mProcessNames.get(processName, uid);
}
void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
@@ -1756,12 +1719,13 @@
private boolean enableNativeHeapZeroInit(ProcessRecord app) {
// Look at the process attribute first.
- if (app.processInfo != null && app.processInfo.nativeHeapZeroInit != null) {
- return app.processInfo.nativeHeapZeroInit;
+ if (app.processInfo != null
+ && app.processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return app.processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
}
// Then at the application attribute.
- if (app.info.isNativeHeapZeroInit() != null) {
- return app.info.isNativeHeapZeroInit();
+ if (app.info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return app.info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
}
// Compat feature last.
if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_ZERO_INIT, app.info)) {
@@ -2408,12 +2372,11 @@
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,
- Runnable crashHandler) {
+ String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
long startTime = SystemClock.uptimeMillis();
ProcessRecord app;
if (!isolated) {
- app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
+ app = getProcessRecordLocked(processName, info.uid);
checkSlow(startTime, "startProcess: after getProcessRecord");
if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
@@ -2476,12 +2439,8 @@
ProcessList.killProcessGroup(app.uid, app.getPid());
checkSlow(startTime, "startProcess: done killing old proc");
- if (!app.isKilled()
- || mService.mAppProfiler.isLastMemoryLevelNormal()
- || 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
+ if (!app.isKilled()) {
+ // Throw a wtf if it's not killed
Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process");
} else {
Slog.w(TAG_PROCESSES, app.toString() + " is attached to a previous process");
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 42e7ff4..cb23b89 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -479,7 +479,7 @@
if (procInfo != null && procInfo.deniedPermissions == null
&& procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
&& procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
- && procInfo.nativeHeapZeroInit == null) {
+ && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) {
// If this process hasn't asked for permissions to be denied, or for a
// non-default GwpAsan mode, or any other non-default setting, then we don't
// care about it.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 143a1cf..31183cac 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1641,6 +1641,13 @@
* PIN or pattern.
*/
private boolean maybeUnlockUser(final @UserIdInt int userId) {
+ if (mInjector.isFileEncryptedNativeOnly() && mLockPatternUtils.isSecure(userId)) {
+ // A token is needed, so don't bother trying to unlock without one.
+ // This keeps misleading error messages from being logged.
+ Slog.d(TAG, "Not unlocking user " + userId
+ + "'s CE storage yet because a credential token is needed");
+ return false;
+ }
// Try unlocking storage using empty token
return unlockUserCleared(userId, null, null, null);
}
@@ -3101,5 +3108,11 @@
protected IStorageManager getStorageManager() {
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
+
+ // This is needed because isFileEncryptedNativeOnly is a static method,
+ // but it needs to be mocked out in tests.
+ protected boolean isFileEncryptedNativeOnly() {
+ return StorageManager.isFileEncryptedNativeOnly();
+ }
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 2ee41ac..57de708 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -16,6 +16,17 @@
package com.android.server.app;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+
+import static com.android.server.wm.CompatModePackages.DOWNSCALED;
+import static com.android.server.wm.CompatModePackages.DOWNSCALE_50;
+import static com.android.server.wm.CompatModePackages.DOWNSCALE_60;
+import static com.android.server.wm.CompatModePackages.DOWNSCALE_70;
+import static com.android.server.wm.CompatModePackages.DOWNSCALE_80;
+import static com.android.server.wm.CompatModePackages.DOWNSCALE_90;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -23,27 +34,41 @@
import android.app.GameManager;
import android.app.GameManager.GameMode;
import android.app.IGameManagerService;
+import android.compat.Compatibility;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ShellCallback;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
import android.util.ArrayMap;
-import android.util.Log;
+import android.util.KeyValueListParser;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.internal.compat.IPlatformCompat;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import java.io.FileDescriptor;
+import java.util.HashSet;
+import java.util.List;
/**
* Service to manage game related features.
@@ -60,14 +85,20 @@
static final int WRITE_SETTINGS = 1;
static final int REMOVE_SETTINGS = 2;
+ static final int POPULATE_GAME_MODE_SETTINGS = 3;
static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds
private final Context mContext;
- private final PackageManager mPackageManager;
private final Object mLock = new Object();
+ private final Object mDeviceConfigLock = new Object();
private final Handler mHandler;
+ private final PackageManager mPackageManager;
+ private final IPlatformCompat mPlatformCompat;
+ private DeviceConfigListener mDeviceConfigListener;
@GuardedBy("mLock")
private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
+ @GuardedBy("mDeviceConfigLock")
+ private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
public GameManagerService(Context context) {
this(context, createServiceThread().getLooper());
@@ -76,7 +107,9 @@
GameManagerService(Context context, Looper looper) {
mContext = context;
mHandler = new SettingsHandler(looper);
- mPackageManager = context.getPackageManager();
+ mPackageManager = mContext.getPackageManager();
+ mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
}
@Override
@@ -143,10 +176,186 @@
}
break;
}
+ case POPULATE_GAME_MODE_SETTINGS: {
+ removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
+ loadDeviceConfigLocked();
+ break;
+ }
}
}
}
+ private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
+
+ DeviceConfigListener() {
+ super();
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_GAME_OVERLAY,
+ mContext.getMainExecutor(), this);
+ }
+
+ @Override
+ public void onPropertiesChanged(Properties properties) {
+ synchronized (mDeviceConfigLock) {
+ for (String key : properties.getKeyset()) {
+ try {
+ // Check if the package is installed before caching it.
+ final String packageName = keyToPackageName(key);
+ mPackageManager.getPackageInfo(packageName, 0);
+ final GamePackageConfiguration config =
+ GamePackageConfiguration.fromProperties(key, properties);
+ if (config.isValid()) {
+ putConfig(config);
+ } else {
+ // This means that we received a bad config, or the config was deleted.
+ Slog.i(TAG, "Removing config for: " + packageName);
+ mConfigs.remove(packageName);
+ disableCompatScale(packageName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DEBUG) {
+ Slog.v(TAG, "Package name not found", e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void finalize() {
+ DeviceConfig.removeOnPropertiesChangedListener(this);
+ }
+ }
+
+ private static class GameModeConfiguration {
+ public static final String TAG = "GameManagerService_GameModeConfiguration";
+ public static final String MODE_KEY = "mode";
+ public static final String SCALING_KEY = "downscaleFactor";
+
+ private final @GameMode int mGameMode;
+ private final String mScaling;
+
+ private GameModeConfiguration(@NonNull int gameMode,
+ @NonNull String scaling) {
+ mGameMode = gameMode;
+ mScaling = scaling;
+ }
+
+ public static GameModeConfiguration fromKeyValueListParser(KeyValueListParser parser) {
+ return new GameModeConfiguration(
+ parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED),
+ parser.getString(SCALING_KEY, "1.0")
+ );
+ }
+
+ public int getGameMode() {
+ return mGameMode;
+ }
+
+ public String getScaling() {
+ return mScaling;
+ }
+
+ public boolean isValid() {
+ return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
+ || mGameMode == GameManager.GAME_MODE_BATTERY) && getCompatChangeId() != 0;
+ }
+
+ public String toString() {
+ return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
+ }
+
+ public long getCompatChangeId() {
+ switch (mScaling) {
+ case "0.5":
+ return DOWNSCALE_50;
+ case "0.6":
+ return DOWNSCALE_60;
+ case "0.7":
+ return DOWNSCALE_70;
+ case "0.8":
+ return DOWNSCALE_80;
+ case "0.9":
+ return DOWNSCALE_90;
+ }
+ return 0;
+ }
+ }
+
+ private static class GamePackageConfiguration {
+ public static final String TAG = "GameManagerService_GamePackageConfiguration";
+
+ private final String mPackageName;
+ private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
+
+ private GamePackageConfiguration(String keyName) {
+ mPackageName = keyToPackageName(keyName);
+ mModeConfigs = new ArrayMap<>();
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public @GameMode int[] getAvailableGameModes() {
+ if (mModeConfigs.keySet().size() > 0) {
+ return mModeConfigs.keySet().stream()
+ .mapToInt(Integer::intValue).toArray();
+ }
+ return new int[]{GameManager.GAME_MODE_UNSUPPORTED};
+ }
+
+ /**
+ * Get a GameModeConfiguration for a given game mode.
+ *
+ * @return The package's GameModeConfiguration for the provided mode or null if absent
+ */
+ public GameModeConfiguration getGameModeConfiguration(@GameMode int gameMode) {
+ return mModeConfigs.get(gameMode);
+ }
+
+ /**
+ * Insert a new GameModeConfiguration
+ */
+ public void addModeConfig(GameModeConfiguration config) {
+ if (config.isValid()) {
+ mModeConfigs.put(config.getGameMode(), config);
+ } else {
+ Slog.w(TAG, "Invalid game mode config for "
+ + mPackageName + ":" + config.toString());
+ }
+ }
+
+ /**
+ * Create a new instance from a package name and DeviceConfig.Properties instance
+ */
+ public static GamePackageConfiguration fromProperties(String key,
+ Properties properties) {
+ final GamePackageConfiguration packageConfig = new GamePackageConfiguration(key);
+ final String configString = properties.getString(key, "");
+ final String[] gameModeConfigStrings = configString.split(":");
+ for (String gameModeConfigString : gameModeConfigStrings) {
+ try {
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ parser.setString(gameModeConfigString);
+ final GameModeConfiguration config =
+ GameModeConfiguration.fromKeyValueListParser(parser);
+ packageConfig.addModeConfig(config);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Invalid config string");
+ }
+ }
+ return packageConfig;
+ }
+
+ public boolean isValid() {
+ return mModeConfigs.size() > 0;
+ }
+
+ public String toString() {
+ return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]";
+ }
+ }
+
/**
* SystemService lifecycle for GameService.
*
@@ -163,6 +372,8 @@
public void onStart() {
mService = new GameManagerService(getContext());
publishBinderService(Context.GAME_SERVICE, mService);
+ mService.registerDeviceConfigListener();
+ mService.registerPackageReceiver();
}
@Override
@@ -200,10 +411,27 @@
}
}
+ /**
+ * Get an array of game modes available for a given package.
+ * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ */
+ @Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException {
+ checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+ synchronized (mDeviceConfigLock) {
+ final GamePackageConfiguration config = mConfigs.get(packageName);
+ if (config == null) {
+ return new int[]{GameManager.GAME_MODE_UNSUPPORTED};
+ }
+ return config.getAvailableGameModes();
+ }
+ }
+
private @GameMode int getGameModeFromSettings(String packageName, int userId) {
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
- Log.w(TAG, "User ID '" + userId + "' does not have a Game Mode"
+ Slog.w(TAG, "User ID '" + userId + "' does not have a Game Mode"
+ " selected for package: '" + packageName + "'");
return GameManager.GAME_MODE_UNSUPPORTED;
}
@@ -229,7 +457,7 @@
final ApplicationInfo applicationInfo = mPackageManager
.getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
- Log.e(TAG, "Ignoring attempt to get the Game Mode for '" + packageName
+ Slog.e(TAG, "Ignoring attempt to get the Game Mode for '" + packageName
+ "' which is not categorized as a game: applicationInfo.flags = "
+ applicationInfo.flags + ", category = " + applicationInfo.category);
return GameManager.GAME_MODE_UNSUPPORTED;
@@ -269,7 +497,7 @@
final ApplicationInfo applicationInfo = mPackageManager
.getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
- Log.e(TAG, "Ignoring attempt to set the Game Mode for '" + packageName
+ Slog.e(TAG, "Ignoring attempt to set the Game Mode for '" + packageName
+ "' which is not categorized as a game: applicationInfo.flags = "
+ applicationInfo.flags + ", category = " + applicationInfo.category);
return;
@@ -295,6 +523,7 @@
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
+ updateCompatModeDownscale(packageName, gameMode);
}
/**
@@ -303,6 +532,8 @@
@VisibleForTesting
void onBootCompleted() {
Slog.d(TAG, "onBootCompleted");
+ final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
+ mHandler.sendMessage(msg);
}
void onUserStarting(int userId) {
@@ -329,6 +560,187 @@
}
}
+ private void loadDeviceConfigLocked() {
+ final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0);
+ final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName))
+ .toArray(String[]::new);
+ synchronized (mDeviceConfigLock) {
+ final Properties properties = DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY, packageNames);
+ for (String key : properties.getKeyset()) {
+ final GamePackageConfiguration config =
+ GamePackageConfiguration.fromProperties(key, properties);
+ putConfig(config);
+ }
+ }
+ }
+
+ private void disableCompatScale(String packageName) {
+ final long uid = Binder.clearCallingIdentity();
+ try {
+ final HashSet<Long> disabledSet = new HashSet<>();
+ disabledSet.add(DOWNSCALED);
+ final CompatibilityChangeConfig changeConfig = new CompatibilityChangeConfig(
+ new Compatibility.ChangeConfig(new HashSet<>(), disabledSet));
+ // TODO: switch to new API provided by aosp/1599153 once merged
+ try {
+ mPlatformCompat.setOverridesForTest(changeConfig, packageName);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Missing compat override permission", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to call IPlatformCompat#setOverridesForTest", e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(uid);
+ }
+ }
+
+ private void enableCompatScale(String packageName, long scaleId) {
+ final long uid = Binder.clearCallingIdentity();
+ try {
+ final HashSet<Long> disabledSet = new HashSet<>();
+ final HashSet<Long> enabledSet = new HashSet<>();
+ disabledSet.add(DOWNSCALE_50);
+ disabledSet.add(DOWNSCALE_60);
+ disabledSet.add(DOWNSCALE_70);
+ disabledSet.add(DOWNSCALE_80);
+ disabledSet.add(DOWNSCALE_90);
+ disabledSet.remove(scaleId);
+ enabledSet.add(DOWNSCALED);
+ enabledSet.add(scaleId);
+ final CompatibilityChangeConfig changeConfig = new CompatibilityChangeConfig(
+ new Compatibility.ChangeConfig(enabledSet, disabledSet));
+ // TODO: switch to new API provided by aosp/1599153 once merged
+ try {
+ mPlatformCompat.setOverridesForTest(changeConfig, packageName);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Missing compat override permission", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to call IPlatformCompat#setOverridesForTest", e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(uid);
+ }
+ }
+
+ private void updateCompatModeDownscale(String packageName, @GameMode int gameMode) {
+ synchronized (mDeviceConfigLock) {
+ if (gameMode == GameManager.GAME_MODE_STANDARD
+ || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+ disableCompatScale(packageName);
+ Slog.v(TAG, "Disabling downscale");
+ return;
+ }
+ if (DEBUG) {
+ Slog.v(TAG, dumpDeviceConfigs());
+ }
+ final GamePackageConfiguration packageConfig = mConfigs.get(packageName);
+ if (packageConfig == null) {
+ Slog.w(TAG, "Package configuration not found for " + packageName);
+ return;
+ }
+ final GameModeConfiguration modeConfig = packageConfig.getGameModeConfiguration(
+ gameMode);
+ if (modeConfig == null) {
+ Slog.w(TAG, "Game mode " + gameMode + " not found for " + packageName);
+ return;
+ }
+ long scaleId = modeConfig.getCompatChangeId();
+ if (scaleId == 0) {
+ Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for "
+ + packageName);
+ return;
+ }
+ Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName);
+ enableCompatScale(packageName, scaleId);
+ }
+ }
+
+ private void putConfig(GamePackageConfiguration config) {
+ if (config.isValid()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Adding config: " + config.toString());
+ }
+ mConfigs.put(config.getPackageName(), config);
+ } else {
+ Slog.w(TAG, "Invalid package config for "
+ + config.getPackageName() + ":" + config.toString());
+ }
+ }
+
+ private void registerPackageReceiver() {
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(ACTION_PACKAGE_CHANGED);
+ packageFilter.addAction(ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme("package");
+ final BroadcastReceiver packageReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
+ final Uri data = intent.getData();
+ try {
+ final String packageName = data.getSchemeSpecificPart();
+ switch (intent.getAction()) {
+ case ACTION_PACKAGE_ADDED:
+ case ACTION_PACKAGE_CHANGED:
+ synchronized (mDeviceConfigLock) {
+ Properties properties = DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY,
+ packageNameToKey(packageName));
+ for (String key : properties.getKeyset()) {
+ GamePackageConfiguration config =
+ GamePackageConfiguration.fromProperties(key,
+ properties);
+ putConfig(config);
+ }
+ }
+ break;
+ case ACTION_PACKAGE_REMOVED:
+ disableCompatScale(packageName);
+ mConfigs.remove(packageName);
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ } catch (NullPointerException e) {
+ Slog.e(TAG, "Failed to get package name for new package", e);
+ }
+ }
+ };
+ mContext.registerReceiver(packageReceiver, packageFilter);
+ }
+
+ private void registerDeviceConfigListener() {
+ mDeviceConfigListener = new DeviceConfigListener();
+ }
+
+ /**
+ * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use
+ * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing
+ * sequence of '_', then we replace all '.' chars with '_';
+ */
+ private static String packageNameToKey(String name) {
+ return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_");
+ }
+
+ /**
+ * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the
+ * resulting special case '_.' with just '_' to get the original package name.
+ */
+ private static String keyToPackageName(String key) {
+ return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_");
+ }
+
+ private String dumpDeviceConfigs() {
+ StringBuilder out = new StringBuilder();
+ for (String key : mConfigs.keySet()) {
+ out.append("[\nName: ").append(key)
+ .append("\nConfig: ").append(mConfigs.get(key).toString()).append("\n]");
+ }
+ return out.toString();
+ }
+
private static ServiceThread createServiceThread() {
ServiceThread handlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 3cfaaf7..677ea5d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -52,7 +52,8 @@
*
* @param clientMonitor Reference of the ClientMonitor that is starting.
*/
- default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {}
+ default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ }
/**
* Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
@@ -63,10 +64,11 @@
* @param clientMonitor Reference of the ClientMonitor that finished.
* @param success True if the operation completed successfully.
*/
- default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {}
+ default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+ }
}
- protected final int mSequentialId;
+ private final int mSequentialId;
@NonNull private final Context mContext;
private final int mTargetUserId;
@NonNull private final String mOwner;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 07d173c..0c883b0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -92,7 +92,7 @@
@Override
protected void startHalOperation() {
try {
- mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, mOperationId);
+ mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 0eb51fd..58eb3ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -160,7 +160,7 @@
features = new byte[0];
}
- mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
+ mCancellationSignal = getFreshDaemon().enroll(
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
EnrollmentType.DEFAULT, features, mPreviewSurface);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 7a846f5..8cbb896 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -42,7 +42,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().generateChallenge(mSequentialId);
+ getFreshDaemon().generateChallenge();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to generateChallenge", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 773647b..af826c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -56,7 +56,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getAuthenticatorId(mSequentialId);
+ getFreshDaemon().getAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
index 75888a5..0ece884 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
@@ -48,7 +48,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().enumerateEnrollments(mSequentialId);
+ getFreshDaemon().enumerateEnrollments();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enumerate", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
index 855ee1d..405e2b2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -40,7 +40,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().invalidateAuthenticatorId(mSequentialId);
+ getFreshDaemon().invalidateAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 48796c1..ba678f3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -53,7 +53,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds);
+ getFreshDaemon().removeEnrollments(mBiometricIds);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting remove", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index ce728bc..5e57950 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -71,7 +71,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken);
+ getFreshDaemon().resetLockout(mHardwareAuthToken);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to reset lockout", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 2863f9f..2294173 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -45,7 +45,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().revokeChallenge(mSequentialId, mChallenge);
+ getFreshDaemon().revokeChallenge(mChallenge);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to revokeChallenge", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 8d3853b..06328e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -44,7 +44,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().close(mSequentialId);
+ getFreshDaemon().close();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
getCallback().onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 768f464..ee36775 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -138,11 +138,6 @@
}
@Override
- public void onStateChanged(int cookie, byte state) {
- // TODO(b/162973174)
- }
-
- @Override
public void onChallengeGenerated(long challenge) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
@@ -535,7 +530,7 @@
if (mCurrentSession != null && mCurrentSession.mSession != null) {
// TODO(181984005): This should be scheduled instead of directly invoked
Slog.d(mTag, "Closing old session");
- mCurrentSession.mSession.close(888 /* cookie */);
+ mCurrentSession.mSession.close();
}
} catch (RemoteException e) {
Slog.e(mTag, "RemoteException", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 36327bb..c63af7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -22,7 +22,6 @@
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.ISessionCallback;
import android.hardware.biometrics.face.SensorProps;
-import android.hardware.biometrics.face.SessionState;
import android.hardware.common.NativeHandle;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
@@ -45,21 +44,21 @@
return new ISession.Stub() {
@Override
- public void generateChallenge(int cookie) throws RemoteException {
- Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+ public void generateChallenge() throws RemoteException {
+ Slog.w(TAG, "generateChallenge");
cb.onChallengeGenerated(0L);
}
@Override
- public void revokeChallenge(int cookie, long challenge) throws RemoteException {
- Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+ public void revokeChallenge(long challenge) throws RemoteException {
+ Slog.w(TAG, "revokeChallenge: " + challenge);
cb.onChallengeRevoked(challenge);
}
@Override
- public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
+ public ICancellationSignal enroll(HardwareAuthToken hat,
byte enrollmentType, byte[] features, NativeHandle previewSurface) {
- Slog.w(TAG, "enroll, cookie: " + cookie);
+ Slog.w(TAG, "enroll");
return new ICancellationSignal.Stub() {
@Override
public void cancel() throws RemoteException {
@@ -69,8 +68,8 @@
}
@Override
- public ICancellationSignal authenticate(int cookie, long operationId) {
- Slog.w(TAG, "authenticate, cookie: " + cookie);
+ public ICancellationSignal authenticate(long operationId) {
+ Slog.w(TAG, "authenticate");
return new ICancellationSignal.Stub() {
@Override
public void cancel() throws RemoteException {
@@ -80,8 +79,8 @@
}
@Override
- public ICancellationSignal detectInteraction(int cookie) {
- Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+ public ICancellationSignal detectInteraction() {
+ Slog.w(TAG, "detectInteraction");
return new ICancellationSignal.Stub() {
@Override
public void cancel() throws RemoteException {
@@ -91,51 +90,51 @@
}
@Override
- public void enumerateEnrollments(int cookie) throws RemoteException {
- Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+ public void enumerateEnrollments() throws RemoteException {
+ Slog.w(TAG, "enumerateEnrollments");
cb.onEnrollmentsEnumerated(new int[0]);
}
@Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
- Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+ public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+ Slog.w(TAG, "removeEnrollments");
cb.onEnrollmentsRemoved(enrollmentIds);
}
@Override
- public void getFeatures(int cookie, int enrollmentId) throws RemoteException {
- Slog.w(TAG, "getFeatures, cookie: " + cookie);
+ public void getFeatures(int enrollmentId) throws RemoteException {
+ Slog.w(TAG, "getFeatures");
cb.onFeaturesRetrieved(new byte[0], enrollmentId);
}
@Override
- public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
+ public void setFeature(HardwareAuthToken hat, int enrollmentId,
byte feature, boolean enabled) throws RemoteException {
- Slog.w(TAG, "setFeature, cookie: " + cookie);
+ Slog.w(TAG, "setFeature");
cb.onFeatureSet(enrollmentId, feature);
}
@Override
- public void getAuthenticatorId(int cookie) throws RemoteException {
- Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+ public void getAuthenticatorId() throws RemoteException {
+ Slog.w(TAG, "getAuthenticatorId");
cb.onAuthenticatorIdRetrieved(0L);
}
@Override
- public void invalidateAuthenticatorId(int cookie) throws RemoteException {
- Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+ public void invalidateAuthenticatorId() throws RemoteException {
+ Slog.w(TAG, "invalidateAuthenticatorId");
cb.onAuthenticatorIdInvalidated(0L);
}
@Override
- public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
- Slog.w(TAG, "resetLockout, cookie: " + cookie);
+ public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+ Slog.w(TAG, "resetLockout");
cb.onLockoutCleared();
}
@Override
- public void close(int cookie) throws RemoteException {
- Slog.w(TAG, "close, cookie: " + cookie);
+ public void close() throws RemoteException {
+ Slog.w(TAG, "close");
cb.onSessionClosed();
}
};
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 50756c8..79e361f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -528,6 +528,8 @@
}
}
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
mSensorId, mCurrentChallengeOwner);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 76a47d3..4e5d12d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -92,7 +92,7 @@
UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this),
mUdfpsOverlayController, this);
try {
- mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, mOperationId);
+ mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 620a9cf..9e9d0ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -74,7 +74,7 @@
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD,
mUdfpsOverlayController, this);
try {
- mCancellationSignal = getFreshDaemon().detectInteraction(mSequentialId);
+ mCancellationSignal = getFreshDaemon().detectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting finger detect", e);
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 63fa66c..fd4aece 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -123,7 +123,7 @@
UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
mUdfpsOverlayController, this);
try {
- mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
+ mCancellationSignal = getFreshDaemon().enroll(
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enroll", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 3c9cced..83c6421 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -44,7 +44,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().generateChallenge(mSequentialId);
+ getFreshDaemon().generateChallenge();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to generateChallenge", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index ce1a318..ed2345e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -56,7 +56,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getAuthenticatorId(mSequentialId);
+ getFreshDaemon().getAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
index c930360..e20544a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
@@ -48,7 +48,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().enumerateEnrollments(mSequentialId);
+ getFreshDaemon().enumerateEnrollments();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enumerate", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
index 80d1a0f..6cd2ef1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -40,7 +40,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().invalidateAuthenticatorId(mSequentialId);
+ getFreshDaemon().invalidateAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index c622208..9a9d6ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -54,7 +54,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds);
+ getFreshDaemon().removeEnrollments(mBiometricIds);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting remove", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index adffba2..b00c592 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -71,7 +71,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken);
+ getFreshDaemon().resetLockout(mHardwareAuthToken);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to reset lockout", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index ebb4fe6..d9bf1c3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -45,7 +45,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().revokeChallenge(mSequentialId, mChallenge);
+ getFreshDaemon().revokeChallenge(mChallenge);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to revokeChallenge", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index ba81357..7055d65 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -44,7 +44,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().close(mSequentialId);
+ getFreshDaemon().close();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
getCallback().onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index cd12d02..4862d849 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -136,11 +136,6 @@
}
@Override
- public void onStateChanged(int cookie, byte state) {
- // TODO(b/162973174)
- }
-
- @Override
public void onChallengeGenerated(long challenge) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
@@ -515,7 +510,7 @@
if (mCurrentSession != null && mCurrentSession.mSession != null) {
// TODO(181984005): This should be scheduled instead of directly invoked
Slog.d(mTag, "Closing old session");
- mCurrentSession.mSession.close(999 /* cookie */);
+ mCurrentSession.mSession.close();
}
} catch (RemoteException e) {
Slog.e(mTag, "RemoteException", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 31fc068..abc3597 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -22,7 +22,6 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.ISessionCallback;
import android.hardware.biometrics.fingerprint.SensorProps;
-import android.hardware.biometrics.fingerprint.SessionState;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
import android.util.Slog;
@@ -45,20 +44,20 @@
return new ISession.Stub() {
@Override
- public void generateChallenge(int cookie) throws RemoteException {
- Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+ public void generateChallenge() throws RemoteException {
+ Slog.w(TAG, "generateChallenge");
cb.onChallengeGenerated(0L);
}
@Override
- public void revokeChallenge(int cookie, long challenge) throws RemoteException {
- Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+ public void revokeChallenge(long challenge) throws RemoteException {
+ Slog.w(TAG, "revokeChallenge: " + challenge);
cb.onChallengeRevoked(challenge);
}
@Override
- public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
- Slog.w(TAG, "enroll, cookie: " + cookie);
+ public ICancellationSignal enroll(HardwareAuthToken hat) {
+ Slog.w(TAG, "enroll");
return new ICancellationSignal.Stub() {
@Override
public void cancel() throws RemoteException {
@@ -68,8 +67,8 @@
}
@Override
- public ICancellationSignal authenticate(int cookie, long operationId) {
- Slog.w(TAG, "authenticate, cookie: " + cookie);
+ public ICancellationSignal authenticate(long operationId) {
+ Slog.w(TAG, "authenticate");
return new ICancellationSignal.Stub() {
@Override
public void cancel() throws RemoteException {
@@ -79,8 +78,8 @@
}
@Override
- public ICancellationSignal detectInteraction(int cookie) {
- Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+ public ICancellationSignal detectInteraction() {
+ Slog.w(TAG, "detectInteraction");
return new ICancellationSignal.Stub() {
@Override
public void cancel() throws RemoteException {
@@ -90,38 +89,38 @@
}
@Override
- public void enumerateEnrollments(int cookie) throws RemoteException {
- Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+ public void enumerateEnrollments() throws RemoteException {
+ Slog.w(TAG, "enumerateEnrollments");
cb.onEnrollmentsEnumerated(new int[0]);
}
@Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
- Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+ public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+ Slog.w(TAG, "removeEnrollments");
cb.onEnrollmentsRemoved(enrollmentIds);
}
@Override
- public void getAuthenticatorId(int cookie) throws RemoteException {
- Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+ public void getAuthenticatorId() throws RemoteException {
+ Slog.w(TAG, "getAuthenticatorId");
cb.onAuthenticatorIdRetrieved(0L);
}
@Override
- public void invalidateAuthenticatorId(int cookie) throws RemoteException {
- Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+ public void invalidateAuthenticatorId() throws RemoteException {
+ Slog.w(TAG, "invalidateAuthenticatorId");
cb.onAuthenticatorIdInvalidated(0L);
}
@Override
- public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
- Slog.w(TAG, "resetLockout, cookie: " + cookie);
+ public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+ Slog.w(TAG, "resetLockout");
cb.onLockoutCleared();
}
@Override
- public void close(int cookie) throws RemoteException {
- Slog.w(TAG, "close, cookie: " + cookie);
+ public void close() throws RemoteException {
+ Slog.w(TAG, "close");
cb.onSessionClosed();
}
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 00b39f1..cd24576 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -15,17 +15,33 @@
*/
package com.android.server.camera;
+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.os.Build.VERSION_CODES.M;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.TaskStackListener;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
import android.hardware.CameraSessionStats;
import android.hardware.CameraStreamStats;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceProxy;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.metrics.LogMaker;
import android.nfc.INfcAdapter;
@@ -41,7 +57,10 @@
import android.stats.camera.nano.CameraProtos.CameraStreamProto;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
import com.android.internal.annotations.GuardedBy;
import com.android.framework.protobuf.nano.MessageNano;
@@ -61,6 +80,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -203,6 +223,63 @@
}
}
+ private final TaskStateHandler mTaskStackListener = new TaskStateHandler();
+
+ private final class TaskInfo {
+ private int frontTaskId;
+ private boolean isResizeable;
+ private boolean isFixedOrientationLandscape;
+ private boolean isFixedOrientationPortrait;
+ private int displayId;
+ }
+
+ private final class TaskStateHandler extends TaskStackListener {
+ private final Object mMapLock = new Object();
+
+ // maps the current top level task id to its corresponding package name
+ @GuardedBy("mMapLock")
+ private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>();
+
+ @Override
+ public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
+ throws RemoteException {
+ synchronized (mMapLock) {
+ TaskInfo info = new TaskInfo();
+ info.frontTaskId = taskInfo.taskId;
+ info.isResizeable = taskInfo.isResizeable;
+ info.displayId = taskInfo.displayId;
+ info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape(
+ taskInfo.topActivityInfo.screenOrientation);
+ info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait(
+ taskInfo.topActivityInfo.screenOrientation);
+ mTaskInfoMap.put(taskInfo.topActivityInfo.packageName, info);
+ }
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) throws RemoteException {
+ synchronized (mMapLock) {
+ for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){
+ if (entry.getValue().frontTaskId == taskId) {
+ mTaskInfoMap.remove(entry.getKey());
+ break;
+ }
+ }
+ }
+ }
+
+ public @Nullable TaskInfo getFrontTaskInfo(String packageName) {
+ synchronized (mMapLock) {
+ if (mTaskInfoMap.containsKey(packageName)) {
+ return mTaskInfoMap.get(packageName);
+ }
+ }
+
+ Log.e(TAG, "Top task with package name: " + packageName + " not found!");
+ return null;
+ }
+ };
+
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -229,6 +306,102 @@
};
private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
+ private boolean isMOrBelow(Context ctx, String packageName) {
+ try {
+ return ctx.getPackageManager().getPackageInfo(
+ packageName, 0).applicationInfo.targetSdkVersion <= M;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG,"Package name not found!");
+ }
+ return false;
+ }
+
+ /**
+ * Gets whether crop-rotate-scale is needed.
+ */
+ private boolean getNeedCropRotateScale(Context ctx, String packageName,
+ @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing) {
+ if (taskInfo == null) {
+ return false;
+ }
+
+ // External cameras do not need crop-rotate-scale.
+ if (lensFacing != CameraMetadata.LENS_FACING_FRONT
+ && lensFacing != CameraMetadata.LENS_FACING_BACK) {
+ Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled.");
+ return false;
+ }
+
+ // Only enable the crop-rotate-scale workaround if the app targets M or below and is not
+ // resizeable.
+ if ((ctx != null) && !isMOrBelow(ctx, packageName) && taskInfo.isResizeable) {
+ Slog.v(TAG,
+ "The activity is N or above and claims to support resizeable-activity. "
+ + "Crop-rotate-scale is disabled.");
+ return false;
+ }
+
+ DisplayManager displayManager = ctx.getSystemService(DisplayManager.class);
+ Display display = displayManager.getDisplay(taskInfo.displayId);
+ int rotation = display.getRotation();
+ int rotationDegree = 0;
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ rotationDegree = 0;
+ break;
+ case Surface.ROTATION_90:
+ rotationDegree = 90;
+ break;
+ case Surface.ROTATION_180:
+ rotationDegree = 180;
+ break;
+ case Surface.ROTATION_270:
+ rotationDegree = 270;
+ break;
+ }
+
+ // Here we only need to know whether the camera is landscape or portrait. Therefore we
+ // don't need to consider whether it is a front or back camera. The formula works for
+ // both.
+ boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0);
+ Slog.v(TAG,
+ "Display.getRotation()=" + rotationDegree
+ + " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation
+ + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait
+ + " isFixedOrientationLandscape=" +
+ taskInfo.isFixedOrientationLandscape);
+ // We need to do crop-rotate-scale when camera is landscape and activity is portrait or
+ // vice versa.
+ if ((taskInfo.isFixedOrientationPortrait && landscapeCamera)
+ || (taskInfo.isFixedOrientationLandscape && !landscapeCamera)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation,
+ int lensFacing) {
+ if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
+ Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " +
+ " camera service UID!");
+ return false;
+ }
+
+ // A few remaining todos:
+ // 1) Do the same check when working in WM compatible mode. The sequence needs
+ // to be adjusted and use orientation events as triggers for all active camera
+ // clients.
+ // 2) Modify the sensor orientation in camera characteristics along with any 3A regions
+ // in capture requests/results to account for thea physical rotation. The former
+ // is somewhat tricky as it assumes that camera clients always check for the current
+ // value by retrieving the camera characteristics from the camera device.
+ return getNeedCropRotateScale(mContext, packageName,
+ mTaskStackListener.getFrontTaskInfo(packageName), sensorOrientation,
+ lensFacing);
+ }
+
@Override
public void pingForUserUpdate() {
if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
@@ -350,6 +523,12 @@
publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy);
publishLocalService(CameraServiceProxy.class, this);
+ try {
+ ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register task stack listener!");
+ }
+
CameraStatsJobService.schedule(mContext);
}
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index ae9b001..d29a0c7 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -23,7 +23,9 @@
import android.annotation.Nullable;
import android.app.compat.PackageOverride;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -60,6 +62,15 @@
static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id.
/**
+ * An overridable change ID to be used only in the CTS test for this SystemApi
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ static final long CTS_SYSTEM_API_OVERRIDABLE_CHANGEID = 174043039; // This is a bug id.
+
+
+ /**
* Callback listener for when compat changes are updated for a package.
* See {@link #registerListener(ChangeListener)} for more details.
*/
@@ -211,6 +222,7 @@
boolean hasPackageOverride(String pname) {
return mRawOverrides.containsKey(pname);
}
+
/**
* Remove any package override for the given package name, restoring the default behaviour.
*
@@ -355,7 +367,7 @@
override.setPackageName(entry.getKey());
override.setMinVersionCode(entry.getValue().getMinVersionCode());
override.setMaxVersionCode(entry.getValue().getMaxVersionCode());
- override.setEnabled(entry.getValue().getEnabled());
+ override.setEnabled(entry.getValue().isEnabled());
rawList.add(override);
}
changeOverrides.setRaw(rawOverrides);
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index ef86f42..55e2696 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -304,6 +304,16 @@
}
/**
+ * Returns whether the change is overridable.
+ */
+ boolean isOverridable(long changeId) {
+ synchronized (mChanges) {
+ CompatChange c = mChanges.get(changeId);
+ return c != null && c.getOverridable();
+ }
+ }
+
+ /**
* Removes an override previously added via {@link #addOverride(long, String, boolean)}.
*
* <p>This restores the default behaviour for the given change and app, once any app processes
@@ -343,7 +353,7 @@
/**
* Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
- * {@link #addOverrides(CompatibilityChangeConfig, String)} for a certain package.
+ * {@link #addOverrides(CompatibilityOverrideConfig, String)} for a certain package.
*
* <p>This restores the default behaviour for the given app.
*
@@ -632,8 +642,11 @@
}
boolean shouldInvalidateCache = false;
for (CompatChange c: changes) {
+ if (!c.hasPackageOverride(packageName)) {
+ continue;
+ }
OverrideAllowedState allowedState =
- mOverrideValidator.getOverrideAllowedState(c.getId(), packageName);
+ mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(), packageName);
shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, mContext);
}
if (shouldInvalidateCache) {
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
index aa66a1a..b500691 100644
--- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -16,6 +16,9 @@
package com.android.server.compat;
+import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
import static com.android.internal.compat.OverrideAllowedState.DEFERRED_VERIFICATION;
import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
@@ -24,6 +27,7 @@
import static com.android.internal.compat.OverrideAllowedState.LOGGING_ONLY_CHANGE;
import static com.android.internal.compat.OverrideAllowedState.PLATFORM_TOO_OLD;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -69,8 +73,25 @@
mForceNonDebuggableFinalBuild = false;
}
+ /**
+ * Check the allowed state for the given changeId and packageName on a recheck.
+ *
+ * <p>Recheck happens when the given app is getting updated. In this case we cannot do a
+ * permission check on the caller, so we're using the fact that the override was present as
+ * proof that the original caller was allowed to set this override.
+ */
+ OverrideAllowedState getOverrideAllowedStateForRecheck(long changeId,
+ @NonNull String packageName) {
+ return getOverrideAllowedStateInternal(changeId, packageName, true);
+ }
+
@Override
public OverrideAllowedState getOverrideAllowedState(long changeId, String packageName) {
+ return getOverrideAllowedStateInternal(changeId, packageName, false);
+ }
+
+ private OverrideAllowedState getOverrideAllowedStateInternal(long changeId, String packageName,
+ boolean isRecheck) {
if (mCompatConfig.isLoggingOnly(changeId)) {
return new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1);
}
@@ -99,6 +120,16 @@
} catch (NameNotFoundException e) {
return new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1);
}
+ // If the change is annotated as @Overridable, apps with the specific permission can
+ // set the override even on production builds. When rechecking the override, e.g. during an
+ // app update we can bypass this check, as it wouldn't have been here in the first place.
+ if (mCompatConfig.isOverridable(changeId)
+ && (isRecheck
+ || mContext.checkCallingOrSelfPermission(
+ OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
+ == PERMISSION_GRANTED)) {
+ return new OverrideAllowedState(ALLOWED, -1, -1);
+ }
int appTargetSdk = applicationInfo.targetSdkVersion;
// Only allow overriding debuggable apps.
if ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
@@ -130,5 +161,4 @@
void forceNonDebuggableFinalForTest(boolean value) {
mForceNonDebuggableFinalBuild = value;
}
-
}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 2be39aa..62de369 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.LOG_COMPAT_CHANGE;
import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG;
+import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD;
import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -182,11 +183,12 @@
}
@Override
- public void setOverridesFromInstaller(CompatibilityOverrideConfig overrides,
+ public void setOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
String packageName) {
- checkCompatChangeOverridePermission();
+ // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
+ checkCompatChangeOverrideOverridablePermission();
+ checkAllCompatOverridesAreOverridable(overrides);
mCompatConfig.addOverrides(overrides, packageName);
- killPackage(packageName);
}
@Override
@@ -383,6 +385,26 @@
}
}
+ private void checkCompatChangeOverrideOverridablePermission() {
+ // Don't check for permissions within the system process
+ if (Binder.getCallingUid() == SYSTEM_UID) {
+ return;
+ }
+ if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException("Cannot override compat change");
+ }
+ }
+
+ private void checkAllCompatOverridesAreOverridable(CompatibilityOverrideConfig overrides) {
+ for (Long changeId : overrides.overrides.keySet()) {
+ if (!mCompatConfig.isOverridable(changeId)) {
+ throw new SecurityException("Only change ids marked as Overridable can be "
+ + "overridden.");
+ }
+ }
+ }
+
private void checkCompatChangeReadAndLogPermission() {
checkCompatChangeReadPermission();
checkCompatChangeLogPermission();
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 103ab95..97df5bf 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -49,6 +49,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -576,6 +577,28 @@
}
}
+ /**
+ * Notify the NetworkAgent that the network is successfully connected.
+ */
+ public void onNetworkCreated() {
+ try {
+ networkAgent.onNetworkCreated();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending network created event", e);
+ }
+ }
+
+ /**
+ * Notify the NetworkAgent that the network is disconnected and destroyed.
+ */
+ public void onNetworkDisconnected() {
+ try {
+ networkAgent.onNetworkDisconnected();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending network disconnected event", e);
+ }
+ }
+
// TODO: consider moving out of NetworkAgentInfo into its own class
private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub {
private final Handler mHandler;
@@ -633,7 +656,13 @@
@Override
public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
- mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes);
+ mQosCallbackTracker.sendEventEpsQosSessionAvailable(qosCallbackId, session, attributes);
+ }
+
+ @Override
+ public void sendNrQosSessionAvailable(final int qosCallbackId, final QosSession session,
+ final NrQosSessionAttributes attributes) {
+ mQosCallbackTracker.sendEventNrQosSessionAvailable(qosCallbackId, session, attributes);
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java
index 0f5400d..534dbe7 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java
@@ -27,6 +27,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import java.util.Objects;
@@ -146,13 +147,23 @@
mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId);
}
- void sendEventQosSessionAvailable(final QosSession session,
+ void sendEventEpsQosSessionAvailable(final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
try {
- if (DBG) log("sendEventQosSessionAvailable: sending...");
+ if (DBG) log("sendEventEpsQosSessionAvailable: sending...");
mCallback.onQosEpsBearerSessionAvailable(session, attributes);
} catch (final RemoteException e) {
- loge("sendEventQosSessionAvailable: remote exception", e);
+ loge("sendEventEpsQosSessionAvailable: remote exception", e);
+ }
+ }
+
+ void sendEventNrQosSessionAvailable(final QosSession session,
+ final NrQosSessionAttributes attributes) {
+ try {
+ if (DBG) log("sendEventNrQosSessionAvailable: sending...");
+ mCallback.onNrQosSessionAvailable(session, attributes);
+ } catch (final RemoteException e) {
+ loge("sendEventNrQosSessionAvailable: remote exception", e);
}
}
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
index 8bda532..b6ab47b 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import com.android.net.module.util.CollectionUtils;
@@ -179,17 +180,31 @@
}
/**
- * Called when the NetworkAgent sends the qos session available event
+ * Called when the NetworkAgent sends the qos session available event for EPS
*
* @param qosCallbackId the callback id that the qos session is now available to
* @param session the qos session that is now available
* @param attributes the qos attributes that are now available on the qos session
*/
- public void sendEventQosSessionAvailable(final int qosCallbackId,
+ public void sendEventEpsQosSessionAvailable(final int qosCallbackId,
final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
- runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ",
- ac -> ac.sendEventQosSessionAvailable(session, attributes));
+ runOnAgentConnection(qosCallbackId, "sendEventEpsQosSessionAvailable: ",
+ ac -> ac.sendEventEpsQosSessionAvailable(session, attributes));
+ }
+
+ /**
+ * Called when the NetworkAgent sends the qos session available event for NR
+ *
+ * @param qosCallbackId the callback id that the qos session is now available to
+ * @param session the qos session that is now available
+ * @param attributes the qos attributes that are now available on the qos session
+ */
+ public void sendEventNrQosSessionAvailable(final int qosCallbackId,
+ final QosSession session,
+ final NrQosSessionAttributes attributes) {
+ runOnAgentConnection(qosCallbackId, "sendEventNrQosSessionAvailable: ",
+ ac -> ac.sendEventNrQosSessionAvailable(session, attributes));
}
/**
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
new file mode 100644
index 0000000..8ce7b66
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.Display;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Saves brightness to a persistent data store, enabling each logical display to have its own
+ * brightness.
+ */
+public class BrightnessSetting {
+ private static final String TAG = "BrightnessSetting";
+
+ private static final int MSG_BRIGHTNESS_CHANGED = 1;
+ private static final Uri BRIGHTNESS_FLOAT_URI =
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT);
+ private final PersistentDataStore mPersistentDataStore;
+
+ private final boolean mIsDefaultDisplay;
+ private final Context mContext;
+ private final LogicalDisplay mLogicalDisplay;
+ private final Object mLock = new Object();
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_BRIGHTNESS_CHANGED) {
+ float brightnessVal = Float.intBitsToFloat(msg.arg1);
+ notifyListeners(brightnessVal);
+ }
+ }
+ };
+
+ private final ContentObserver mBrightnessSettingsObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (selfChange) {
+ return;
+ }
+ if (BRIGHTNESS_FLOAT_URI.equals(uri)) {
+ float brightness = getScreenBrightnessSettingFloat();
+ setBrightness(brightness, true);
+ }
+ }
+ };
+
+ private final CopyOnWriteArrayList<BrightnessSettingListener> mListeners =
+ new CopyOnWriteArrayList<BrightnessSettingListener>();
+
+ private float mBrightness;
+
+ BrightnessSetting(@NonNull PersistentDataStore persistentDataStore,
+ @NonNull LogicalDisplay logicalDisplay,
+ @NonNull Context context) {
+ mPersistentDataStore = persistentDataStore;
+ mLogicalDisplay = logicalDisplay;
+ mContext = context;
+ mIsDefaultDisplay = mLogicalDisplay.getDisplayIdLocked() == Display.DEFAULT_DISPLAY;
+ mBrightness = mPersistentDataStore.getBrightness(
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked());
+ if (mIsDefaultDisplay) {
+ mContext.getContentResolver().registerContentObserver(BRIGHTNESS_FLOAT_URI,
+ false, mBrightnessSettingsObserver);
+ }
+ }
+
+ /**
+ * Returns the brightness from the brightness setting
+ *
+ * @return brightness for the current display
+ */
+ public float getBrightness() {
+ return mBrightness;
+ }
+
+ /**
+ * Registers listener for brightness setting change events.
+ */
+ public void registerListener(BrightnessSettingListener l) {
+ if (!mListeners.contains(l)) {
+ mListeners.add(l);
+ }
+ }
+
+ /**
+ * Unregisters listener for brightness setting change events.
+ *
+ * @param l listener
+ */
+ public void unregisterListener(BrightnessSettingListener l) {
+ mListeners.remove(l);
+ }
+
+ void setBrightness(float brightness) {
+ setBrightness(brightness, false);
+ }
+
+ private void setBrightness(float brightness, boolean isFromSystemSetting) {
+ if (brightness == mBrightness) {
+ return;
+ }
+ if (Float.isNaN(brightness)) {
+ Slog.w(TAG, "Attempting to set invalid brightness");
+ return;
+ }
+ synchronized (mLock) {
+
+ mBrightness = brightness;
+
+ // If it didn't come from us
+ if (mIsDefaultDisplay && !isFromSystemSetting) {
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightness,
+ UserHandle.USER_CURRENT);
+ }
+ mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
+ brightness);
+ int toSend = Float.floatToIntBits(mBrightness);
+ Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED, toSend, 0);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private float getScreenBrightnessSettingFloat() {
+ return Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void notifyListeners(float brightness) {
+ for (BrightnessSettingListener l : mListeners) {
+ l.onBrightnessChanged(brightness);
+ }
+ }
+
+ /**
+ * Listener for changes to system brightness.
+ */
+ public interface BrightnessSettingListener {
+
+ /**
+ * Notify that the brightness has changed.
+ */
+ void onBrightnessChanged(float brightness);
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c010906..e38d91c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1955,9 +1955,12 @@
if (mBrightnessTracker == null) {
mBrightnessTracker = new BrightnessTracker(mContext, null);
}
+
+ final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
+ display, mContext);
final DisplayPowerController displayPowerController = new DisplayPowerController(
mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager,
- mDisplayBlanker, display, mBrightnessTracker);
+ mDisplayBlanker, display, mBrightnessTracker, brightnessSetting);
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
@@ -2662,6 +2665,48 @@
}
@Override // Binder call
+ public void setBrightness(int displayId, float brightness) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+ "Permission required to set the display's brightness");
+ if (!isValidBrightness(brightness)) {
+ Slog.w(TAG, "Attempted to set invalid brightness" + brightness);
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+ if (dpc != null) {
+ dpc.putScreenBrightnessSetting(brightness);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public float getBrightness(int displayId) {
+ float brightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+ "Permission required to set the display's brightness");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+ if (dpc != null) {
+ brightness = dpc.getScreenBrightnessSetting();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return brightness;
+ }
+
+ @Override // Binder call
public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
@@ -2809,6 +2854,13 @@
Slog.w(TAG, msg);
return false;
}
+
+ }
+
+ private static boolean isValidBrightness(float brightness) {
+ return !Float.isNaN(brightness)
+ && (brightness >= PowerManager.BRIGHTNESS_MIN)
+ && (brightness <= PowerManager.BRIGHTNESS_MAX);
}
private final class LocalService extends DisplayManagerInternal {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index d1d0496..48edb73 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -16,13 +16,11 @@
package com.android.server.display;
-import android.Manifest;
import android.content.Context;
import android.content.Intent;
-import android.os.Binder;
+import android.hardware.display.DisplayManager;
import android.os.ShellCommand;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.view.Display;
import java.io.PrintWriter;
@@ -111,17 +109,8 @@
}
final Context context = mService.getContext();
- context.enforceCallingOrSelfPermission(
- Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
- "Permission required to set the display's brightness");
- final long token = Binder.clearCallingIdentity();
- try {
- Settings.System.putFloatForUser(context.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightness,
- UserHandle.USER_CURRENT);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ dm.setBrightness(Display.DEFAULT_DISPLAY, brightness);
return 0;
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 645ca7a..4bbd338 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -263,6 +263,8 @@
highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE;
}
+ // We try to find a range of priorities which define a non-empty set of allowed display
+ // modes. Each time we fail we increase the lowest priority.
while (lowestConsideredPriority <= highestConsideredPriority) {
summarizeVotes(
votes, lowestConsideredPriority, highestConsideredPriority, primarySummary);
@@ -343,8 +345,15 @@
}
if (baseModeId == INVALID_DISPLAY_MODE_ID) {
- throw new IllegalStateException("Can't select a base display mode for display "
- + displayId + ". The votes are " + mVotesByDisplay.valueAt(displayId));
+ Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling"
+ + " back to the default mode. Display = " + displayId + ", votes = " + votes
+ + ", supported modes = " + Arrays.toString(modes));
+
+ float fps = defaultMode.getRefreshRate();
+ return new DesiredDisplayModeSpecs(defaultMode.getModeId(),
+ /*allowGroupSwitching */ false,
+ new RefreshRateRange(fps, fps),
+ new RefreshRateRange(fps, fps));
}
if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7110d3e..56ad01b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -122,6 +122,7 @@
private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
private static final int MSG_IGNORE_PROXIMITY = 8;
private static final int MSG_STOP = 9;
+ private static final int MSG_UPDATE_BRIGHTNESS = 10;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -355,13 +356,14 @@
private final HighBrightnessModeController mHbmController;
+ private final BrightnessSetting mBrightnessSetting;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
// The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
private float mInitialAutoBrightness;
-
// The controller for the automatic brightness level.
private AutomaticBrightnessController mAutomaticBrightnessController;
@@ -410,6 +412,7 @@
private ObjectAnimator mColorFadeOnAnimator;
private ObjectAnimator mColorFadeOffAnimator;
private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+ private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
// True if this DisplayPowerController has been stopped and should no longer be running.
private boolean mStopped;
@@ -420,7 +423,7 @@
public DisplayPowerController(Context context,
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
- BrightnessTracker brightnessTracker) {
+ BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting) {
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
mHandler = new DisplayControllerHandler(handler.getLooper());
@@ -439,7 +442,7 @@
mContext = context;
mBrightnessTracker = brightnessTracker;
-
+ mBrightnessSetting = brightnessSetting;
PowerManager pm = context.getSystemService(PowerManager.class);
final Resources resources = context.getResources();
@@ -785,6 +788,10 @@
mAutomaticBrightnessController.stop();
}
+ if (mBrightnessSetting != null) {
+ mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
+ }
+
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
}
}
@@ -831,10 +838,12 @@
if (brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
+ mBrightnessSettingListener = brightnessValue -> {
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
+ mHandler.sendMessage(msg);
+ };
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT),
- false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mBrightnessSetting.registerListener(mBrightnessSettingListener);
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
@@ -1150,7 +1159,7 @@
// before applying the low power or dim transformations so that the slider
// accurately represents the full possible range, even if they range changes what
// it means in absolute terms.
- putScreenBrightnessSetting(brightnessState);
+ putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
}
// Apply dimming by at least some minimum amount when user activity
@@ -1804,7 +1813,6 @@
private void handleSettingsChange(boolean userSwitch) {
mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
-
if (userSwitch) {
// Don't treat user switches as user initiated change.
mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
@@ -1825,10 +1833,11 @@
return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
}
- private float getScreenBrightnessSetting() {
- final float brightness = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, mScreenBrightnessDefault,
- UserHandle.USER_CURRENT);
+ float getScreenBrightnessSetting() {
+ float brightness = mBrightnessSetting.getBrightness();
+ if (Float.isNaN(brightness)) {
+ brightness = mScreenBrightnessDefault;
+ }
return clampAbsoluteBrightness(brightness);
}
@@ -1839,13 +1848,15 @@
return clampScreenBrightnessForVr(brightnessFloat);
}
- private void putScreenBrightnessSetting(float brightnessValue) {
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ void putScreenBrightnessSetting(float brightnessValue) {
+ putScreenBrightnessSetting(brightnessValue, false);
+ }
+
+ private void putScreenBrightnessSetting(float brightnessValue, boolean updateCurrent) {
+ if (updateCurrent) {
mCurrentScreenBrightnessSetting = brightnessValue;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessValue,
- UserHandle.USER_CURRENT);
}
+ mBrightnessSetting.setBrightness(brightnessValue);
}
private void putAutoBrightnessAdjustmentSetting(float adjustment) {
@@ -2175,7 +2186,7 @@
}
break;
case MSG_CONFIGURE_BRIGHTNESS:
- mBrightnessConfiguration = (BrightnessConfiguration)msg.obj;
+ mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
updatePowerState();
break;
@@ -2197,6 +2208,12 @@
case MSG_STOP:
cleanupHandlerThreadAfterStop();
break;
+
+ case MSG_UPDATE_BRIGHTNESS:
+ if (mStopped) {
+ return;
+ }
+ handleSettingsChange(false /*userSwitch*/);
}
}
}
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index a62642b..c90ddf4 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -62,6 +62,7 @@
* <display-states>
* <display unique-id="XXXXXXX">
* <color-mode>0</color-mode>
+ * <brightness-value>0</brightness-value>
* </display>
* </display-states>
* <stable-device-values>
@@ -82,7 +83,7 @@
* TODO: refactor this to extract common code shared with the input manager's data store
*/
final class PersistentDataStore {
- static final String TAG = "DisplayManager";
+ static final String TAG = "DisplayManager.PersistentDataStore";
private static final String TAG_DISPLAY_MANAGER_STATE = "display-manager-state";
@@ -95,6 +96,7 @@
private static final String TAG_DISPLAY_STATES = "display-states";
private static final String TAG_DISPLAY = "display";
private static final String TAG_COLOR_MODE = "color-mode";
+ private static final String TAG_BRIGHTNESS_VALUE = "brightness-value";
private static final String ATTR_UNIQUE_ID = "unique-id";
private static final String TAG_STABLE_DEVICE_VALUES = "stable-device-values";
@@ -255,6 +257,30 @@
return false;
}
+ public float getBrightness(DisplayDevice device) {
+ if (device == null || !device.hasStableUniqueId()) {
+ return Float.NaN;
+ }
+ final DisplayState state = getDisplayState(device.getUniqueId(), false);
+ if (state == null) {
+ return Float.NaN;
+ }
+ return state.getBrightness();
+ }
+
+ public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+ final String displayDeviceUniqueId = displayDevice.getUniqueId();
+ if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+ return false;
+ }
+ final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
+ if (state.setBrightness(brightness)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
public Point getStableDisplaySize() {
loadIfNeeded();
return mStableDeviceValues.getDisplaySize();
@@ -473,6 +499,7 @@
private static final class DisplayState {
private int mColorMode;
+ private float mBrightness;
public boolean setColorMode(int colorMode) {
if (colorMode == mColorMode) {
@@ -486,14 +513,33 @@
return mColorMode;
}
+ public boolean setBrightness(float brightness) {
+ if (brightness == mBrightness) {
+ return false;
+ }
+ mBrightness = brightness;
+ return true;
+ }
+
+ public float getBrightness() {
+ return mBrightness;
+ }
+
+
public void loadFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals(TAG_COLOR_MODE)) {
- String value = parser.nextText();
- mColorMode = Integer.parseInt(value);
+ switch (parser.getName()) {
+ case TAG_COLOR_MODE:
+ String value = parser.nextText();
+ mColorMode = Integer.parseInt(value);
+ break;
+ case TAG_BRIGHTNESS_VALUE:
+ String brightness = parser.nextText();
+ mBrightness = Float.parseFloat(brightness);
+ break;
}
}
}
@@ -502,10 +548,15 @@
serializer.startTag(null, TAG_COLOR_MODE);
serializer.text(Integer.toString(mColorMode));
serializer.endTag(null, TAG_COLOR_MODE);
+ serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
+ serializer.text(Float.toString(mBrightness));
+ serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
+
}
public void dump(final PrintWriter pw, final String prefix) {
pw.println(prefix + "ColorMode=" + mColorMode);
+ pw.println(prefix + "BrightnessValue=" + mBrightness);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c9364c6..27f8fd3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4830,6 +4830,9 @@
setInputMethodEnabledLocked(defaultImiId, true);
}
}
+
+ updateDefaultVoiceImeIfNeededLocked();
+
// Here is not the perfect place to reset the switching controller. Ideally
// mSwitchingController and mSettings should be able to share the same state.
// TODO: Make sure that mSwitchingController and mSettings are sharing the
@@ -4842,6 +4845,37 @@
mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget();
}
+ @GuardedBy("mMethodMap")
+ private void updateDefaultVoiceImeIfNeededLocked() {
+ final String systemSpeechRecognizer =
+ mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
+ final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+ final InputMethodInfo newSystemVoiceIme = InputMethodUtils.chooseSystemVoiceIme(
+ mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId);
+ if (newSystemVoiceIme == null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
+ + " this may be expected.");
+ }
+ // Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings
+ // does not update the actual Secure Settings until the user is unlocked.
+ if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
+ mSettings.putDefaultVoiceInputMethod("");
+ // We don't support disabling the voice ime when a package is removed from the
+ // config.
+ }
+ return;
+ }
+ if (TextUtils.equals(currentDefaultVoiceImeId, newSystemVoiceIme.getId())) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
+ }
+ setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
+ mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+ }
+
// ----------------------------------------------------------------------
private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 0e908d4..ac3c31d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -337,6 +337,52 @@
return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
}
+ /**
+ * Chooses an eligible system voice IME from the given IMEs.
+ *
+ * @param methodMap Map from the IME ID to {@link InputMethodInfo}.
+ * @param systemSpeechRecognizerPackageName System speech recognizer configured by the system
+ * config.
+ * @param currentDefaultVoiceImeId IME ID currently set to
+ * {@link Settings.Secure#DEFAULT_VOICE_INPUT_METHOD}
+ * @return {@link InputMethodInfo} that is found in {@code methodMap} and most suitable for
+ * the system voice IME.
+ */
+ @Nullable
+ static InputMethodInfo chooseSystemVoiceIme(
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap,
+ @Nullable String systemSpeechRecognizerPackageName,
+ @Nullable String currentDefaultVoiceImeId) {
+ if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) {
+ return null;
+ }
+ final InputMethodInfo defaultVoiceIme = methodMap.get(currentDefaultVoiceImeId);
+ // If the config matches the package of the setting, use the current one.
+ if (defaultVoiceIme != null && defaultVoiceIme.isSystem()
+ && defaultVoiceIme.getPackageName().equals(systemSpeechRecognizerPackageName)) {
+ return defaultVoiceIme;
+ }
+ InputMethodInfo firstMatchingIme = null;
+ final int methodCount = methodMap.size();
+ for (int i = 0; i < methodCount; ++i) {
+ final InputMethodInfo imi = methodMap.valueAt(i);
+ if (!imi.isSystem()) {
+ continue;
+ }
+ if (!TextUtils.equals(imi.getPackageName(), systemSpeechRecognizerPackageName)) {
+ continue;
+ }
+ if (firstMatchingIme != null) {
+ Slog.e(TAG, "At most one InputMethodService can be published in "
+ + "systemSpeechRecognizer: " + systemSpeechRecognizerPackageName
+ + ". Ignoring all of them.");
+ return null;
+ }
+ firstMatchingIme = imi;
+ }
+ return firstMatchingIme;
+ }
+
static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
boolean checkCountry, String mode) {
if (locale == null) {
@@ -1233,6 +1279,22 @@
return imi;
}
+ void putDefaultVoiceInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
+ }
+
+ @Nullable
+ String getDefaultVoiceInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
+ }
+ return imi;
+ }
+
boolean isSubtypeSelected() {
return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index f173fc7..4d302b1 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -259,13 +259,16 @@
BroadcastReceiver wifiReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())
+ || WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED.equals(
+ intent.getAction())) {
sendWifiSettingUpdate(false /* forceUpdate */);
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED);
mContext.registerReceiver(wifiReceiver, filter);
mContext.getContentResolver().registerContentObserver(
@@ -298,7 +301,7 @@
mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
if (userId == getCurrentUserId()) {
- Log.d(TAG, "User: " + userId + " enabled: " + enabled);
+ Log.d(TAG, "User: " + userId + "mic privacy: " + enabled);
sendMicrophoneDisableSettingUpdate(enabled);
}
});
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 3245fdf..7be47a4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -324,8 +324,11 @@
}
public void onMicrophoneDisableSettingChanged(boolean enabled) {
- sendSettingChanged(android.hardware.contexthub.V1_2.Setting.GLOBAL_MIC_DISABLE,
- enabled ? SettingValue.ENABLED : SettingValue.DISABLED);
+ // The SensorPrivacyManager reports if microphone privacy was enabled,
+ // which translates to microphone access being disabled (and vice-versa).
+ // With this in mind, we flip the argument before piping it to CHRE.
+ sendSettingChanged(android.hardware.contexthub.V1_2.Setting.MICROPHONE,
+ enabled ? SettingValue.DISABLED : SettingValue.ENABLED);
}
private void sendSettingChanged(byte setting, byte newValue) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 4299d9e..3859285 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -422,8 +422,8 @@
private static final int MSG_LIMIT_REACHED = 5;
private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6;
private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7;
- private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
- private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
+ private static final int MSG_UPDATE_INTERFACE_QUOTAS = 10;
+ private static final int MSG_REMOVE_INTERFACE_QUOTAS = 11;
private static final int MSG_POLICIES_CHANGED = 13;
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
@@ -2035,33 +2035,44 @@
final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED;
final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
long limitBytes = Long.MAX_VALUE;
- if (hasLimit && policy.hasCycle()) {
+ long warningBytes = Long.MAX_VALUE;
+ if ((hasLimit || hasWarning) && policy.hasCycle()) {
final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
.cycleIterator(policy).next();
final long start = cycle.first.toInstant().toEpochMilli();
final long end = cycle.second.toInstant().toEpochMilli();
final long totalBytes = getTotalBytes(policy.template, start, end);
- if (policy.lastLimitSnooze < start) {
+ // If the limit notification is not snoozed, the limit quota needs to be calculated.
+ if (hasLimit && policy.lastLimitSnooze < start) {
// remaining "quota" bytes are based on total usage in
// current cycle. kernel doesn't like 0-byte rules, so we
// set 1-byte quota and disable the radio later.
limitBytes = Math.max(1, policy.limitBytes - totalBytes);
}
+
+ // If the warning notification was snoozed by user, or the service already knows
+ // it is over warning bytes, doesn't need to calculate warning bytes.
+ if (hasWarning && policy.lastWarningSnooze < start
+ && !policy.isOverWarning(totalBytes)) {
+ warningBytes = Math.max(1, policy.warningBytes - totalBytes);
+ }
}
- if (hasLimit || policy.metered) {
+ if (hasWarning || hasLimit || policy.metered) {
if (matchingIfaces.size() > 1) {
// TODO: switch to shared quota once NMS supports
Slog.w(TAG, "shared quota unsupported; generating rule for each iface");
}
- // Set the interface limit. For interfaces which has no cycle, or metered with
- // no policy limit, or snoozed limit notification; we still need to put iptables
- // rule hooks to restrict apps for data saver, so push really high quota.
+ // Set the interface warning and limit. For interfaces which has no cycle,
+ // or metered with no policy quotas, or snoozed notification; we still need to put
+ // iptables rule hooks to restrict apps for data saver, so push really high quota.
+ // TODO: Push NetworkStatsProvider.QUOTA_UNLIMITED instead of Long.MAX_VALUE to
+ // providers.
for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
final String iface = matchingIfaces.valueAt(j);
- setInterfaceQuotaAsync(iface, limitBytes);
+ setInterfaceQuotasAsync(iface, warningBytes, limitBytes);
newMeteredIfaces.add(iface);
}
}
@@ -2084,7 +2095,7 @@
for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
final String iface = matchingIfaces.valueAt(j);
if (!newMeteredIfaces.contains(iface)) {
- setInterfaceQuotaAsync(iface, Long.MAX_VALUE);
+ setInterfaceQuotasAsync(iface, Long.MAX_VALUE, Long.MAX_VALUE);
newMeteredIfaces.add(iface);
}
}
@@ -2096,7 +2107,7 @@
for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) {
final String iface = mMeteredIfaces.valueAt(i);
if (!newMeteredIfaces.contains(iface)) {
- removeInterfaceQuotaAsync(iface);
+ removeInterfaceQuotasAsync(iface);
}
}
mMeteredIfaces = newMeteredIfaces;
@@ -5036,19 +5047,20 @@
mNetworkStats.advisePersistThreshold(persistThreshold);
return true;
}
- case MSG_UPDATE_INTERFACE_QUOTA: {
- final String iface = (String) msg.obj;
- // int params need to be stitched back into a long
- final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
- removeInterfaceQuota(iface);
- setInterfaceQuota(iface, quota);
- mNetworkStats.setStatsProviderLimitAsync(iface, quota);
+ case MSG_UPDATE_INTERFACE_QUOTAS: {
+ final IfaceQuotas val = (IfaceQuotas) msg.obj;
+ // TODO: Consider set a new limit before removing the original one.
+ removeInterfaceLimit(val.iface);
+ setInterfaceLimit(val.iface, val.limit);
+ mNetworkStats.setStatsProviderWarningAndLimitAsync(val.iface, val.warning,
+ val.limit);
return true;
}
- case MSG_REMOVE_INTERFACE_QUOTA: {
+ case MSG_REMOVE_INTERFACE_QUOTAS: {
final String iface = (String) msg.obj;
- removeInterfaceQuota(iface);
- mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED);
+ removeInterfaceLimit(iface);
+ mNetworkStats.setStatsProviderWarningAndLimitAsync(iface, QUOTA_UNLIMITED,
+ QUOTA_UNLIMITED);
return true;
}
case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5196,15 +5208,32 @@
}
}
- private void setInterfaceQuotaAsync(String iface, long quotaBytes) {
- // long quotaBytes split up into two ints to fit in message
- mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA, (int) (quotaBytes >> 32),
- (int) (quotaBytes & 0xFFFFFFFF), iface).sendToTarget();
+ private static final class IfaceQuotas {
+ @NonNull public final String iface;
+ // Warning and limit bytes of interface qutoas, could be QUOTA_UNLIMITED or Long.MAX_VALUE
+ // if not set. 0 is not acceptable since kernel doesn't like 0-byte rules.
+ public final long warning;
+ public final long limit;
+
+ private IfaceQuotas(@NonNull String iface, long warning, long limit) {
+ this.iface = iface;
+ this.warning = warning;
+ this.limit = limit;
+ }
}
- private void setInterfaceQuota(String iface, long quotaBytes) {
+ private void setInterfaceQuotasAsync(@NonNull String iface,
+ long warningBytes, long limitBytes) {
+ mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS,
+ new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget();
+ }
+
+ private void setInterfaceLimit(String iface, long limitBytes) {
try {
- mNetworkManager.setInterfaceQuota(iface, quotaBytes);
+ // For legacy design the data warning is covered by global alert, where the
+ // kernel will notify upper layer for a small amount of change of traffic
+ // statistics. Thus, passing warning is not needed.
+ mNetworkManager.setInterfaceQuota(iface, limitBytes);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting interface quota", e);
} catch (RemoteException e) {
@@ -5212,11 +5241,11 @@
}
}
- private void removeInterfaceQuotaAsync(String iface) {
- mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface).sendToTarget();
+ private void removeInterfaceQuotasAsync(String iface) {
+ mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTAS, iface).sendToTarget();
}
- private void removeInterfaceQuota(String iface) {
+ private void removeInterfaceLimit(String iface) {
try {
mNetworkManager.removeInterfaceQuota(iface);
} catch (IllegalStateException e) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 0cb0bc2c..0e9a9da 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -37,8 +37,9 @@
public abstract void forceUpdate();
/**
- * Set the quota limit to all registered custom network stats providers.
+ * Set the warning and limit to all registered custom network stats providers.
* Note that invocation of any interface will be sent to all providers.
*/
- public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota);
+ public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
+ long limit);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 785e487..de5aae0 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1674,11 +1674,14 @@
}
@Override
- public void setStatsProviderLimitAsync(@NonNull String iface, long quota) {
- if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")");
- // TODO: Set warning accordingly.
+ public void setStatsProviderWarningAndLimitAsync(
+ @NonNull String iface, long warning, long limit) {
+ if (LOGV) {
+ Slog.v(TAG, "setStatsProviderWarningAndLimitAsync("
+ + iface + "," + warning + "," + limit + ")");
+ }
invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
- NetworkStatsProvider.QUOTA_UNLIMITED, quota));
+ warning, limit));
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 8c3c423..fd8ec7f 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -2347,7 +2347,7 @@
}
final List<ShortcutInfo> page = new ArrayList<>(results.size());
for (SearchResult result : results) {
- final ShortcutInfo si = new AppSearchShortcutInfo(result.getDocument())
+ final ShortcutInfo si = new AppSearchShortcutInfo(result.getGenericDocument())
.toShortcutInfo(mShortcutUser.getUserId());
page.add(si);
}
@@ -2398,8 +2398,7 @@
@NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
final long callingIdentity = Binder.clearCallingIdentity();
final AppSearchManager.SearchContext searchContext =
- new AppSearchManager.SearchContext.Builder()
- .setDatabaseName(getPackageName()).build();
+ new AppSearchManager.SearchContext.Builder(getPackageName()).build();
final AppSearchSession session;
try {
session = ConcurrentUtils.waitForFutureNoInterrupt(
@@ -2408,6 +2407,8 @@
mAppSearchSession = session;
return cb.apply(mAppSearchSession);
} catch (Exception e) {
+ Slog.e(TAG, "Failed to initiate app search for shortcut package "
+ + getPackageName() + " user " + mShortcutUser.getUserId(), e);
return AndroidFuture.completedFuture(null);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
@@ -2427,7 +2428,8 @@
AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
}
final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
- session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
+ session.setSchema(
+ schemaBuilder.build(), mShortcutUser.mExecutor, mShortcutUser.mExecutor, result -> {
if (!result.isSuccess()) {
future.completeExceptionally(
new IllegalArgumentException(result.getErrorMessage()));
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 0b21487..e1d1c26 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -342,7 +342,7 @@
private final IPackageManager mIPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
- private final UserManagerInternal mUserManagerInternal;
+ final UserManagerInternal mUserManagerInternal;
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
private final IUriGrantsManager mUriGrantsManager;
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 51cb995..ce49d88 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -722,6 +722,12 @@
future.completeExceptionally(new RuntimeException("app search manager is null"));
return future;
}
+ if (!mService.mUserManagerInternal.isUserUnlockingOrUnlocked(getUserId())) {
+ // In rare cases the user might be stopped immediate after it started, in these cases
+ // any on-going session will need to be abandoned.
+ future.completeExceptionally(new RuntimeException("User " + getUserId() + " is "));
+ return future;
+ }
final long callingIdentity = Binder.clearCallingIdentity();
try {
mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> {
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 629f120..21e44ab 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -407,7 +407,7 @@
retProcs.put(proc.getName(),
new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()),
proc.getGwpAsanMode(), proc.getMemtagMode(),
- proc.getNativeHeapZeroInit()));
+ proc.getNativeHeapZeroInitialized()));
}
return retProcs;
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index f0d54b4..e16c67f 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1729,6 +1729,14 @@
@Override
public void grantPermission(@NonNull String permission, @NonNull PackageInfo pkg,
@NonNull UserHandle user) {
+ if (PermissionManager.DEBUG_TRACE_GRANTS
+ && PermissionManager.shouldTraceGrant(
+ pkg.packageName, permission, user.getIdentifier())) {
+ Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS,
+ "PregrantPolicy is granting " + pkg.packageName + " "
+ + permission + " for user " + user.getIdentifier(),
+ new RuntimeException());
+ }
PermissionState state = getPermissionState(permission, pkg, user);
state.initGranted();
state.newGranted = true;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 3244c44..e63426f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1338,7 +1338,7 @@
boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
if (PermissionManager.DEBUG_TRACE_GRANTS
&& PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
- Log.i(TAG, "System is granting " + packageName + " "
+ Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, "System is granting " + packageName + " "
+ permName + " for user " + userId + " on behalf of uid " + callingUid
+ " " + mPackageManagerInt.getNameForUid(callingUid),
new RuntimeException());
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index f4bcd3e..0b48b5c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -70,11 +70,8 @@
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");
+ throw new SecurityException(
+ "Caller is not allowed to query domain verification state");
}
mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 8075bdb..1b2ff08 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -148,6 +148,8 @@
@NonNull
private DomainVerificationProxy mProxy = new DomainVerificationProxyUnavailable();
+ private boolean mCanSendBroadcasts;
+
public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig,
@NonNull PlatformCompat platformCompat) {
super(context);
@@ -181,11 +183,18 @@
@Override
public void onBootPhase(int phase) {
super.onBootPhase(phase);
- if (phase != SystemService.PHASE_BOOT_COMPLETED || !hasRealVerifier()) {
+ if (!hasRealVerifier()) {
return;
}
- verifyPackages(null, false);
+ switch (phase) {
+ case PHASE_ACTIVITY_MANAGER_READY:
+ mCanSendBroadcasts = true;
+ break;
+ case PHASE_BOOT_COMPLETED:
+ verifyPackages(null, false);
+ break;
+ }
}
@Override
@@ -858,7 +867,7 @@
}
if (sendBroadcast) {
- sendBroadcastForPackage(pkgName);
+ sendBroadcast(pkgName);
}
}
@@ -937,7 +946,7 @@
}
if (sendBroadcast && hasAutoVerifyDomains) {
- sendBroadcastForPackage(pkgName);
+ sendBroadcast(pkgName);
}
}
@@ -1098,8 +1107,19 @@
return mCollector;
}
- private void sendBroadcastForPackage(@NonNull String packageName) {
- mProxy.sendBroadcastForPackages(Collections.singleton(packageName));
+ private void sendBroadcast(@NonNull String packageName) {
+ sendBroadcast(Collections.singleton(packageName));
+ }
+
+ private void sendBroadcast(@NonNull Set<String> packageNames) {
+ if (!mCanSendBroadcasts) {
+ // If the system cannot send broadcasts, it's probably still in boot, so dropping this
+ // request should be fine. The verification agent should re-scan packages once boot
+ // completes.
+ return;
+ }
+
+ mProxy.sendBroadcastForPackages(packageNames);
}
private boolean hasRealVerifier() {
@@ -1183,7 +1203,7 @@
}
if (!packagesToBroadcast.isEmpty()) {
- mProxy.sendBroadcastForPackages(packagesToBroadcast);
+ sendBroadcast(packagesToBroadcast);
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 1b5bb95..f185464 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2622,23 +2622,19 @@
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
UserHandle.USER_CURRENT_OR_SELF);
}
- float minFloat = mPowerManager.getBrightnessConstraint(
+ float min = mPowerManager.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- float maxFloat = mPowerManager.getBrightnessConstraint(
+ float max = mPowerManager.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
- float stepFloat = (maxFloat - minFloat) / BRIGHTNESS_STEPS * direction;
- float brightnessFloat = Settings.System.getFloatForUser(
- mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_FLOAT,
- mContext.getDisplay().getBrightnessDefault(),
- UserHandle.USER_CURRENT_OR_SELF);
- brightnessFloat += stepFloat;
+ float step = (max - min) / BRIGHTNESS_STEPS * direction;
+ int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
+ float brightness = mDisplayManager.getBrightness(screenDisplayId);
+ brightness += step;
// Make sure we don't go beyond the limits.
- brightnessFloat = Math.min(maxFloat, brightnessFloat);
- brightnessFloat = Math.max(minFloat, brightnessFloat);
+ brightness = Math.min(max, brightness);
+ brightness = Math.max(min, brightness);
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessFloat,
- UserHandle.USER_CURRENT_OR_SELF);
+ mDisplayManager.setBrightness(screenDisplayId, brightness);
startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
UserHandle.CURRENT_OR_SELF);
}
diff --git a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
index 2fcd178..9732eba3 100644
--- a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
+++ b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
@@ -248,6 +248,30 @@
return false;
}
+ long getLastUserActivityTimeLocked(int groupId) {
+ return mDisplayGroupInfos.get(groupId).lastUserActivityTime;
+ }
+
+ long getLastUserActivityTimeNoChangeLightsLocked(int groupId) {
+ return mDisplayGroupInfos.get(groupId).lastUserActivityTimeNoChangeLights;
+ }
+
+ int getUserActivitySummaryLocked(int groupId) {
+ return mDisplayGroupInfos.get(groupId).userActivitySummary;
+ }
+
+ void setLastUserActivityTimeLocked(int groupId, long time) {
+ mDisplayGroupInfos.get(groupId).lastUserActivityTime = time;
+ }
+
+ void setLastUserActivityTimeNoChangeLightsLocked(int groupId, long time) {
+ mDisplayGroupInfos.get(groupId).lastUserActivityTimeNoChangeLights = time;
+ }
+
+ void setUserActivitySummaryLocked(int groupId, int summary) {
+ mDisplayGroupInfos.get(groupId).userActivitySummary = summary;
+ }
+
/**
* Interface through which an interested party may be informed of {@link DisplayGroup} events.
*/
@@ -265,6 +289,9 @@
public boolean ready;
public long lastPowerOnTime;
public boolean sandmanSummoned;
+ public long lastUserActivityTime;
+ public long lastUserActivityTimeNoChangeLights;
+ public int userActivitySummary;
/** {@code true} if this DisplayGroup supports dreaming; otherwise {@code false}. */
public boolean supportsSandman;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d2a4cd6..54d5b7a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -29,6 +29,7 @@
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+import static android.os.PowerManagerInternal.wakefulnessToString;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -92,6 +93,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import android.view.DisplayInfo;
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
@@ -180,8 +182,8 @@
private static final int DIRTY_VR_MODE_CHANGED = 1 << 13;
// Dirty bit: attentive timer may have timed out
private static final int DIRTY_ATTENTIVE = 1 << 14;
- // Dirty bit: display group power state has changed
- private static final int DIRTY_DISPLAY_GROUP_POWER_UPDATED = 1 << 16;
+ // Dirty bit: display group wakefulness has changed
+ private static final int DIRTY_DISPLAY_GROUP_WAKEFULNESS = 1 << 16;
// Summarizes the state of all active wakelocks.
private static final int WAKE_LOCK_CPU = 1 << 0;
@@ -338,10 +340,6 @@
private @WakeReason int mLastWakeReason;
private int mLastSleepReason;
- // Timestamp of the last call to user activity.
- private long mLastUserActivityTime;
- private long mLastUserActivityTimeNoChangeLights;
-
// Timestamp of last time power boost interaction was sent.
private long mLastInteractivePowerHintTime;
@@ -349,9 +347,6 @@
private long mLastScreenBrightnessBoostTime;
private boolean mScreenBrightnessBoostInProgress;
- // A bitfield that summarizes the effect of the user activity timer.
- private int mUserActivitySummary;
-
// Manages the desired power state of displays. The actual state may lag behind the
// requested because it is updated asynchronously by the display power controller.
private DisplayGroupPowerStateMapper mDisplayGroupPowerStateMapper;
@@ -634,6 +629,13 @@
public void onDisplayGroupEventLocked(int event, int groupId) {
final int oldWakefulness = getWakefulnessLocked();
final int newWakefulness = mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked();
+
+ if (event == DISPLAY_GROUP_ADDED && newWakefulness == WAKEFULNESS_AWAKE) {
+ // Kick user activity to prevent newly added group from timing out instantly.
+ userActivityNoUpdateLocked(groupId, mClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER, /* flags= */ 0, Process.SYSTEM_UID);
+ }
+
if (oldWakefulness != newWakefulness) {
final int reason;
switch (newWakefulness) {
@@ -656,7 +658,7 @@
mContext.getOpPackageName(), "groupId: " + groupId);
}
- mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
+ mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
updatePowerStateLocked();
}
}
@@ -1059,6 +1061,10 @@
private void onFlip(boolean isFaceDown) {
long millisUntilNormalTimeout = 0;
synchronized (mLock) {
+ if (!mBootCompleted) {
+ return;
+ }
+
mIsFaceDown = isFaceDown;
if (isFaceDown) {
final long currentTime = mClock.uptimeMillis();
@@ -1066,8 +1072,9 @@
final long sleepTimeout = getSleepTimeoutLocked(-1L);
final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, -1L);
millisUntilNormalTimeout =
- mLastUserActivityTime + screenOffTimeout - mClock.uptimeMillis();
- userActivityInternal(mClock.uptimeMillis(),
+ mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(
+ Display.DEFAULT_DISPLAY_GROUP) + screenOffTimeout - currentTime;
+ userActivityInternal(Display.DEFAULT_DISPLAY, currentTime,
PowerManager.USER_ACTIVITY_EVENT_FACE_DOWN, /* flags= */0,
Process.SYSTEM_UID);
}
@@ -1645,12 +1652,28 @@
// Called from native code.
private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
- userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID);
+ userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
- private void userActivityInternal(long eventTime, int event, int flags, int uid) {
+ private void userActivityInternal(int displayId, long eventTime, int event, int flags,
+ int uid) {
synchronized (mLock) {
- if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
+ updatePowerStateLocked();
+ }
+ return;
+ }
+
+ final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
+ if (displayInfo == null) {
+ return;
+ }
+ final int groupId = displayInfo.displayGroupId;
+ if (groupId == Display.INVALID_DISPLAY_GROUP) {
+ return;
+ }
+ if (userActivityNoUpdateLocked(groupId, eventTime, event, flags, uid)) {
updatePowerStateLocked();
}
}
@@ -1658,7 +1681,7 @@
private void onUserAttention() {
synchronized (mLock) {
- if (userActivityNoUpdateLocked(mClock.uptimeMillis(),
+ if (userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */,
Process.SYSTEM_UID)) {
updatePowerStateLocked();
@@ -1667,10 +1690,22 @@
}
private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
+ boolean updatePowerState = false;
+ for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ if (userActivityNoUpdateLocked(id, eventTime, event, flags, uid)) {
+ updatePowerState = true;
+ }
+ }
+
+ return updatePowerState;
+ }
+
+ private boolean userActivityNoUpdateLocked(int groupId, long eventTime, int event, int flags,
+ int uid) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime
- + ", event=" + event + ", flags=0x" + Integer.toHexString(flags)
- + ", uid=" + uid);
+ Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
+ + ", eventTime=" + eventTime + ", event=" + event
+ + ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
}
if (eventTime < mLastSleepTime || eventTime < mLastWakeTime || !mSystemReady) {
@@ -1692,8 +1727,9 @@
mOverriddenTimeout = -1;
}
- if (getWakefulnessLocked() == WAKEFULNESS_ASLEEP
- || getWakefulnessLocked() == WAKEFULNESS_DOZING
+ final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
+ if (wakefulness == WAKEFULNESS_ASLEEP
+ || wakefulness == WAKEFULNESS_DOZING
|| (flags & PowerManager.USER_ACTIVITY_FLAG_INDIRECT) != 0) {
return false;
}
@@ -1701,9 +1737,13 @@
maybeUpdateForegroundProfileLastActivityLocked(eventTime);
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
- if (eventTime > mLastUserActivityTimeNoChangeLights
- && eventTime > mLastUserActivityTime) {
- mLastUserActivityTimeNoChangeLights = eventTime;
+ if (eventTime
+ > mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked(
+ groupId)
+ && eventTime > mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(
+ groupId)) {
+ mDisplayGroupPowerStateMapper.setLastUserActivityTimeNoChangeLightsLocked(
+ groupId, eventTime);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -1712,8 +1752,9 @@
return true;
}
} else {
- if (eventTime > mLastUserActivityTime) {
- mLastUserActivityTime = eventTime;
+ if (eventTime > mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(
+ groupId)) {
+ mDisplayGroupPowerStateMapper.setLastUserActivityTimeLocked(groupId, eventTime);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -1778,7 +1819,6 @@
setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
opPackageName, details);
mDisplayGroupPowerStateMapper.setLastPowerOnTimeLocked(groupId, eventTime);
- mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -1829,7 +1869,6 @@
if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid);
}
- mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -1862,7 +1901,6 @@
mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true);
setWakefulnessLocked(groupId, WAKEFULNESS_DREAMING, eventTime, uid, /* reason= */
0, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
- mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
@@ -1890,7 +1928,6 @@
setWakefulnessLocked(groupId, WAKEFULNESS_ASLEEP, eventTime, uid,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, /* opUid= */ 0,
/* opPackageName= */ null, /* details= */ null);
- mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -1901,8 +1938,14 @@
void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason,
int opUid, String opPackageName, String details) {
if (mDisplayGroupPowerStateMapper.setWakefulnessLocked(groupId, wakefulness)) {
+ mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
setGlobalWakefulnessLocked(mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(),
eventTime, reason, uid, opUid, opPackageName, details);
+ if (wakefulness == WAKEFULNESS_AWAKE) {
+ // Kick user activity to prevent newly awake group from timing out instantly.
+ userActivityNoUpdateLocked(
+ groupId, eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
+ }
}
}
@@ -1972,8 +2015,6 @@
switch (wakefulness) {
case WAKEFULNESS_AWAKE:
mNotifier.onWakeUp(reason, details, uid, opPackageName, opUid);
- userActivityNoUpdateLocked(
- eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
if (sQuiescent) {
mDirty |= DIRTY_QUIESCENT;
}
@@ -2163,8 +2204,9 @@
"android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID,
mContext.getOpPackageName(), Process.SYSTEM_UID);
}
- userActivityNoUpdateLocked(
- now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+
+ userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, now,
+ PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
// only play charging sounds if boot is completed so charging sounds don't play
// with potential notification sounds
@@ -2407,106 +2449,123 @@
*/
private void updateUserActivitySummaryLocked(long now, int dirty) {
// Update the status of the user activity timeout timer.
- if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY
- | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) {
- mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
+ if ((dirty & (DIRTY_DISPLAY_GROUP_WAKEFULNESS | DIRTY_WAKE_LOCKS
+ | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) == 0) {
+ return;
+ }
+ mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
- long nextTimeout = 0;
- if (getWakefulnessLocked() == WAKEFULNESS_AWAKE
- || getWakefulnessLocked() == WAKEFULNESS_DREAMING
- || getWakefulnessLocked() == WAKEFULNESS_DOZING) {
- final long attentiveTimeout = getAttentiveTimeoutLocked();
- final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
- long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
- attentiveTimeout);
- final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
- screenOffTimeout =
- getScreenOffTimeoutWithFaceDownLocked(screenOffTimeout, screenDimDuration);
- final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
- final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
-
- mUserActivitySummary = 0;
- if (mLastUserActivityTime >= mLastWakeTime) {
- nextTimeout = mLastUserActivityTime
- + screenOffTimeout - screenDimDuration;
- if (now < nextTimeout) {
- mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
+ final long attentiveTimeout = getAttentiveTimeoutLocked();
+ final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+ long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
+ attentiveTimeout);
+ final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+ screenOffTimeout =
+ getScreenOffTimeoutWithFaceDownLocked(screenOffTimeout, screenDimDuration);
+ final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
+ long nextTimeout = -1;
+ boolean hasUserActivitySummary = false;
+ for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ int groupUserActivitySummary = 0;
+ long groupNextTimeout = 0;
+ if (mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != WAKEFULNESS_ASLEEP) {
+ final long lastUserActivityTime =
+ mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(groupId);
+ final long lastUserActivityTimeNoChangeLights =
+ mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked(
+ groupId);
+ if (lastUserActivityTime >= mLastWakeTime) {
+ groupNextTimeout = lastUserActivityTime + screenOffTimeout - screenDimDuration;
+ if (now < groupNextTimeout) {
+ groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
} else {
- nextTimeout = mLastUserActivityTime + screenOffTimeout;
- if (now < nextTimeout) {
- mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
+ groupNextTimeout = lastUserActivityTime + screenOffTimeout;
+ if (now < groupNextTimeout) {
+ groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
}
}
}
- if (mUserActivitySummary == 0
- && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
- nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
- if (now < nextTimeout) {
+ if (groupUserActivitySummary == 0
+ && lastUserActivityTimeNoChangeLights >= mLastWakeTime) {
+ groupNextTimeout = lastUserActivityTimeNoChangeLights + screenOffTimeout;
+ if (now < groupNextTimeout) {
final DisplayPowerRequest displayPowerRequest =
- mDisplayGroupPowerStateMapper.getPowerRequestLocked(
- Display.DEFAULT_DISPLAY_GROUP);
+ mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId);
if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
|| displayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
- mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
+ groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
} else if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
- mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
+ groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
}
}
}
- if (mUserActivitySummary == 0) {
+ if (groupUserActivitySummary == 0) {
if (sleepTimeout >= 0) {
- final long anyUserActivity = Math.max(mLastUserActivityTime,
- mLastUserActivityTimeNoChangeLights);
+ final long anyUserActivity = Math.max(lastUserActivityTime,
+ lastUserActivityTimeNoChangeLights);
if (anyUserActivity >= mLastWakeTime) {
- nextTimeout = anyUserActivity + sleepTimeout;
- if (now < nextTimeout) {
- mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
+ groupNextTimeout = anyUserActivity + sleepTimeout;
+ if (now < groupNextTimeout) {
+ groupUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
}
}
} else {
- mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
- nextTimeout = -1;
+ groupUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
+ groupNextTimeout = -1;
}
}
- if (mUserActivitySummary != USER_ACTIVITY_SCREEN_DREAM && userInactiveOverride) {
- if ((mUserActivitySummary &
+ if (groupUserActivitySummary != USER_ACTIVITY_SCREEN_DREAM
+ && userInactiveOverride) {
+ if ((groupUserActivitySummary &
(USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0) {
// Device is being kept awake by recent user activity
- if (nextTimeout >= now && mOverriddenTimeout == -1) {
+ if (mOverriddenTimeout == -1) {
// Save when the next timeout would have occurred
- mOverriddenTimeout = nextTimeout;
+ mOverriddenTimeout = groupNextTimeout;
}
}
- mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
- nextTimeout = -1;
+ groupUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
+ groupNextTimeout = -1;
}
- if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+ if ((groupUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
&& (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) == 0) {
- nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout,
+ groupNextTimeout = mAttentionDetector.updateUserActivity(groupNextTimeout,
screenDimDuration);
}
- if (nextProfileTimeout > 0) {
- nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
- }
+ hasUserActivitySummary |= groupUserActivitySummary != 0;
- if (mUserActivitySummary != 0 && nextTimeout >= 0) {
- scheduleUserInactivityTimeout(nextTimeout);
+ if (nextTimeout == -1) {
+ nextTimeout = groupNextTimeout;
+ } else if (groupNextTimeout != -1) {
+ nextTimeout = Math.min(nextTimeout, groupNextTimeout);
}
- } else {
- mUserActivitySummary = 0;
}
+ mDisplayGroupPowerStateMapper.setUserActivitySummaryLocked(groupId,
+ groupUserActivitySummary);
+
if (DEBUG_SPEW) {
- Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness="
- + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
- + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
- + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout));
+ Slog.d(TAG, "updateUserActivitySummaryLocked: groupId=" + groupId
+ + ", mWakefulness=" + wakefulnessToString(
+ mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId))
+ + ", mUserActivitySummary=0x" + Integer.toHexString(
+ groupUserActivitySummary)
+ + ", nextTimeout=" + TimeUtils.formatUptime(groupNextTimeout));
}
}
+
+ final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
+ if (nextProfileTimeout > 0) {
+ nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
+ }
+
+ if (hasUserActivitySummary && nextTimeout >= 0) {
+ scheduleUserInactivityTimeout(nextTimeout);
+ }
}
private void scheduleUserInactivityTimeout(long timeMs) {
@@ -2539,15 +2598,20 @@
private void updateAttentiveStateLocked(long now, int dirty) {
long attentiveTimeout = getAttentiveTimeoutLocked();
- long goToSleepTime = mLastUserActivityTime + attentiveTimeout;
+ if (attentiveTimeout < 0) {
+ return;
+ }
+ // Attentive state only applies to the default display group.
+ long goToSleepTime = mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(
+ Display.DEFAULT_DISPLAY_GROUP) + attentiveTimeout;
long showWarningTime = goToSleepTime - mAttentiveWarningDurationConfig;
boolean warningDismissed = maybeHideInattentiveSleepWarningLocked(now, showWarningTime);
- if (attentiveTimeout >= 0 && (warningDismissed
- || (dirty & (DIRTY_ATTENTIVE | DIRTY_STAY_ON | DIRTY_SCREEN_BRIGHTNESS_BOOST
- | DIRTY_PROXIMITY_POSITIVE | DIRTY_WAKEFULNESS | DIRTY_BOOT_COMPLETED
- | DIRTY_SETTINGS)) != 0)) {
+ if (warningDismissed ||
+ (dirty & (DIRTY_ATTENTIVE | DIRTY_STAY_ON | DIRTY_SCREEN_BRIGHTNESS_BOOST
+ | DIRTY_PROXIMITY_POSITIVE | DIRTY_WAKEFULNESS | DIRTY_BOOT_COMPLETED
+ | DIRTY_SETTINGS)) != 0) {
if (DEBUG_SPEW) {
Slog.d(TAG, "Updating attentive state");
}
@@ -2601,9 +2665,12 @@
return false;
}
- private boolean isAttentiveTimeoutExpired(long now) {
+ private boolean isAttentiveTimeoutExpired(int groupId, long now) {
long attentiveTimeout = getAttentiveTimeoutLocked();
- return attentiveTimeout >= 0 && now >= mLastUserActivityTime + attentiveTimeout;
+ // Attentive state only applies to the default display group.
+ return groupId == Display.DEFAULT_DISPLAY_GROUP && attentiveTimeout >= 0
+ && now >= mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(groupId)
+ + attentiveTimeout;
}
/**
@@ -2703,26 +2770,20 @@
| DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
| DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS
| DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) {
- if (getWakefulnessLocked() == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
- }
- final long time = mClock.uptimeMillis();
- if (isAttentiveTimeoutExpired(time)) {
- // TODO (b/175764389): Support per-display timeouts.
- for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ final long time = mClock.uptimeMillis();
+ for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ if (mDisplayGroupPowerStateMapper.getWakefulnessLocked(id) == WAKEFULNESS_AWAKE
+ && isItBedTimeYetLocked(id)) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "updateWakefulnessLocked: Bed time for group " + id);
+ }
+ if (isAttentiveTimeoutExpired(id, time)) {
changed = sleepDisplayGroupNoUpdateLocked(id, time,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
- }
- } else if (shouldNapAtBedTimeLocked()) {
- // TODO (b/175764389): Support per-display timeouts.
- for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ } else if (shouldNapAtBedTimeLocked()) {
changed = dreamDisplayGroupNoUpdateLocked(id, time, Process.SYSTEM_UID);
- }
- } else {
- // TODO (b/175764389): Support per-display timeouts.
- for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ } else {
changed = sleepDisplayGroupNoUpdateLocked(id, time,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
}
@@ -2743,51 +2804,50 @@
}
/**
- * Returns true if the device should go to sleep now.
- * Also used when exiting a dream to determine whether we should go back
- * to being fully awake or else go to sleep for good.
+ * Returns true if the DisplayGroup with the provided {@code groupId} should go to sleep now.
+ * Also used when exiting a dream to determine whether we should go back to being fully awake or
+ * else go to sleep for good.
*/
- private boolean isItBedTimeYetLocked() {
+ private boolean isItBedTimeYetLocked(int groupId) {
if (!mBootCompleted) {
return false;
}
long now = mClock.uptimeMillis();
- if (isAttentiveTimeoutExpired(now)) {
- return !isBeingKeptFromInattentiveSleepLocked();
+ if (isAttentiveTimeoutExpired(groupId, now)) {
+ return !isBeingKeptFromInattentiveSleepLocked(groupId);
} else {
- return !isBeingKeptAwakeLocked();
+ return !isBeingKeptAwakeLocked(groupId);
}
}
/**
- * Returns true if the device is being kept awake by a wake lock, user activity
- * or the stay on while powered setting. We also keep the phone awake when
- * the proximity sensor returns a positive result so that the device does not
- * lock while in a phone call. This function only controls whether the device
- * will go to sleep or dream which is independent of whether it will be allowed
- * to suspend.
+ * Returns true if the DisplayGroup with the provided {@code groupId} is being kept awake by a
+ * wake lock, user activity or the stay on while powered setting. We also keep the phone awake
+ * when the proximity sensor returns a positive result so that the device does not lock while in
+ * a phone call. This function only controls whether the device will go to sleep or dream which
+ * is independent of whether it will be allowed to suspend.
*/
- private boolean isBeingKeptAwakeLocked() {
+ private boolean isBeingKeptAwakeLocked(int groupId) {
return mStayOn
|| mProximityPositive
|| (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0
- || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
- | USER_ACTIVITY_SCREEN_DIM)) != 0
+ || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & (
+ USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0
|| mScreenBrightnessBoostInProgress;
}
/**
- * Returns true if the device is prevented from going into inattentive sleep by the stay on
- * while powered setting. We also keep the device awake when the proximity sensor returns a
- * positive result so that the device does not lock while in a phone call. This function only
- * controls whether the device will go to sleep which is independent of whether it will be
- * allowed to suspend.
+ * Returns true if the DisplayGroup with the provided {@code groupId} is prevented from going
+ * into inattentive sleep by the stay on while powered setting. We also keep the device awake
+ * when the proximity sensor returns a positive result so that the device does not lock while in
+ * a phone call. This function only controls whether the device will go to sleep which is
+ * independent of whether it will be allowed to suspend.
*/
- private boolean isBeingKeptFromInattentiveSleepLocked() {
+ private boolean isBeingKeptFromInattentiveSleepLocked(int groupId) {
return mStayOn || mScreenBrightnessBoostInProgress || mProximityPositive
- || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
- | USER_ACTIVITY_SCREEN_DIM)) != 0;
+ || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & (
+ USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0;
}
private boolean isBeingKeptFromShowingInattentiveSleepWarningLocked() {
@@ -2902,7 +2962,7 @@
if (mDreamsBatteryLevelDrainCutoffConfig >= 0
&& mBatteryLevel < mBatteryLevelWhenDreamStarted
- mDreamsBatteryLevelDrainCutoffConfig
- && !isBeingKeptAwakeLocked()) {
+ && !isBeingKeptAwakeLocked(groupId)) {
// If the user activity timeout expired and the battery appears
// to be draining faster than it is charging then stop dreaming
// and go to sleep.
@@ -2917,18 +2977,17 @@
}
// Dream has ended or will be stopped. Update the power state.
- if (isItBedTimeYetLocked()) {
- final int flags = isAttentiveTimeoutExpired(now)
+ if (isItBedTimeYetLocked(groupId)) {
+ final int flags = isAttentiveTimeoutExpired(groupId, now)
? PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE : 0;
sleepDisplayGroupNoUpdateLocked(groupId, now,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags, Process.SYSTEM_UID);
- updatePowerStateLocked();
} else {
wakeDisplayGroupNoUpdateLocked(groupId, now, PowerManager.WAKE_REASON_UNKNOWN,
"android.server.power:DREAM_FINISHED", Process.SYSTEM_UID,
mContext.getOpPackageName(), Process.SYSTEM_UID);
- updatePowerStateLocked();
}
+ updatePowerStateLocked();
} else if (wakefulness == WAKEFULNESS_DOZING) {
if (isDreaming) {
return; // continue dozing
@@ -2952,17 +3011,18 @@
private boolean canDreamLocked(int groupId) {
final DisplayPowerRequest displayPowerRequest =
mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId);
- if (getWakefulnessLocked() != WAKEFULNESS_DREAMING
+ if (!mBootCompleted
+ || getWakefulnessLocked() != WAKEFULNESS_DREAMING
|| !mDreamsSupportedConfig
|| !mDreamsEnabledSetting
|| !displayPowerRequest.isBrightOrDim()
|| displayPowerRequest.isVr()
- || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
- | USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0
- || !mBootCompleted) {
+ || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & (
+ USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM
+ | USER_ACTIVITY_SCREEN_DREAM)) == 0) {
return false;
}
- if (!isBeingKeptAwakeLocked()) {
+ if (!isBeingKeptAwakeLocked(groupId)) {
if (!mIsPowered && !mDreamsEnabledOnBatteryConfig) {
return false;
}
@@ -2971,11 +3031,9 @@
&& mBatteryLevel < mDreamsBatteryLevelMinimumWhenNotPoweredConfig) {
return false;
}
- if (mIsPowered
- && mDreamsBatteryLevelMinimumWhenPoweredConfig >= 0
- && mBatteryLevel < mDreamsBatteryLevelMinimumWhenPoweredConfig) {
- return false;
- }
+ return !mIsPowered
+ || mDreamsBatteryLevelMinimumWhenPoweredConfig < 0
+ || mBatteryLevel >= mDreamsBatteryLevelMinimumWhenPoweredConfig;
}
return true;
}
@@ -3002,7 +3060,7 @@
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS
| DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
| DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
- DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_POWER_UPDATED)) != 0) {
+ DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) != 0) {
if ((dirty & DIRTY_QUIESCENT) != 0) {
if (mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
sQuiescent = false;
@@ -3072,7 +3130,7 @@
mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId))
+ ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)
+ ", mUserActivitySummary=0x" + Integer.toHexString(
- mUserActivitySummary)
+ mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId))
+ ", mBootCompleted=" + mBootCompleted
+ ", screenBrightnessOverride="
+ displayPowerRequest.screenBrightnessOverride
@@ -3156,8 +3214,9 @@
}
if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
- || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
|| !mBootCompleted
+ || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId)
+ & USER_ACTIVITY_SCREEN_BRIGHT) != 0
|| mScreenBrightnessBoostInProgress) {
return DisplayPowerRequest.POLICY_BRIGHT;
}
@@ -3190,7 +3249,7 @@
synchronized (mLock) {
mProximityPositive = false;
mDirty |= DIRTY_PROXIMITY_POSITIVE;
- userActivityNoUpdateLocked(mClock.uptimeMillis(),
+ userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
updatePowerStateLocked();
}
@@ -3758,7 +3817,7 @@
mScreenBrightnessBoostInProgress = true;
mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST;
- userActivityNoUpdateLocked(eventTime,
+ userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, eventTime,
PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
updatePowerStateLocked();
}
@@ -3863,14 +3922,16 @@
@VisibleForTesting
boolean wasDeviceIdleForInternal(long ms) {
synchronized (mLock) {
- return mLastUserActivityTime + ms < mClock.uptimeMillis();
+ return mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(
+ Display.DEFAULT_DISPLAY_GROUP) + ms < mClock.uptimeMillis();
}
}
@VisibleForTesting
void onUserActivity() {
synchronized (mLock) {
- mLastUserActivityTime = mClock.uptimeMillis();
+ mDisplayGroupPowerStateMapper.setLastUserActivityTimeLocked(
+ Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis());
}
}
@@ -4024,7 +4085,6 @@
TimeUtils.formatDuration(mNotifyLongNextCheck, mClock.uptimeMillis(), pw);
}
pw.println();
- pw.println(" mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary));
pw.println(" mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity);
pw.println(" mSandmanScheduled=" + mSandmanScheduled);
pw.println(" mBatteryLevelLow=" + mBatteryLevelLow);
@@ -4035,9 +4095,6 @@
pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime));
pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime));
pw.println(" mLastSleepReason=" + PowerManager.sleepReasonToString(mLastSleepReason));
- pw.println(" mLastUserActivityTime=" + TimeUtils.formatUptime(mLastUserActivityTime));
- pw.println(" mLastUserActivityTimeNoChangeLights="
- + TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights));
pw.println(" mLastInteractivePowerHintTime="
+ TimeUtils.formatUptime(mLastInteractivePowerHintTime));
pw.println(" mLastScreenBrightnessBoostTime="
@@ -4181,6 +4238,18 @@
pw.println(profile.mLockingNotified);
}
+ pw.println("Display Group User Activity:");
+ for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ pw.println(" displayGroupId=" + id);
+ pw.println(" userActivitySummary=0x" + Integer.toHexString(
+ mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(id)));
+ pw.println(" lastUserActivityTime=" + TimeUtils.formatUptime(
+ mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(id)));
+ pw.println(" lastUserActivityTimeNoChangeLights=" + TimeUtils.formatUptime(
+ mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked(
+ id)));
+ }
+
wcd = mWirelessChargerDetector;
}
@@ -4266,17 +4335,27 @@
proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
- final long userActivityToken = proto.start(PowerManagerServiceDumpProto.USER_ACTIVITY);
- proto.write(
- PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
- (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0);
- proto.write(
- PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
- (mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0);
- proto.write(
- PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
- (mUserActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0);
- proto.end(userActivityToken);
+ for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+ final long userActivityToken = proto.start(
+ PowerManagerServiceDumpProto.USER_ACTIVITY);
+ proto.write(PowerManagerServiceDumpProto.UserActivityProto.DISPLAY_GROUP_ID, id);
+ final long userActivitySummary =
+ mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(id);
+ proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
+ (userActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0);
+ proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
+ (userActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0);
+ proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
+ (userActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0);
+ proto.write(
+ PowerManagerServiceDumpProto.UserActivityProto.LAST_USER_ACTIVITY_TIME_MS,
+ mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(id));
+ proto.write(
+ PowerManagerServiceDumpProto.UserActivityProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
+ mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked(
+ id));
+ proto.end(userActivityToken);
+ }
proto.write(
PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
@@ -4295,10 +4374,6 @@
proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
- proto.write(PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime);
- proto.write(
- PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
- mLastUserActivityTimeNoChangeLights);
proto.write(
PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
mLastInteractivePowerHintTime);
@@ -5102,7 +5177,7 @@
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- userActivityInternal(eventTime, event, flags, uid);
+ userActivityInternal(displayId, eventTime, event, flags, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 38ad4f0..7b0fb01 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -30,7 +30,6 @@
import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
-import static com.android.server.wm.InsetsSourceProviderProto.FINISH_SEAMLESS_ROTATE_FRAME_NUMBER;
import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
import static com.android.server.wm.InsetsSourceProviderProto.IME_OVERRIDDEN_FRAME;
import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
@@ -60,6 +59,7 @@
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.io.PrintWriter;
+import java.util.function.Consumer;
/**
* Controller for a specific inset source on the server. It's called provider as it provides the
@@ -85,6 +85,16 @@
private final Rect mImeOverrideFrame = new Rect();
private boolean mIsLeashReadyForDispatching;
+ private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
+ if (mControl != null) {
+ final SurfaceControl leash = mControl.getLeash();
+ if (leash != null) {
+ final Point position = mControl.getSurfacePosition();
+ t.setPosition(leash, position.x, position.y);
+ }
+ }
+ };
+
/** The visibility override from the current controlling window. */
private boolean mClientVisible;
@@ -151,7 +161,6 @@
// TODO: Ideally, we should wait for the animation to finish so previous window can
// 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);
@@ -252,12 +261,11 @@
final Point position = getWindowFrameSurfacePosition();
if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
changed = true;
- if (!mWin.getWindowFrames().didFrameSizeChange()) {
- updateLeashPosition(-1 /* frameNumber */);
- } else if (mWin.mInRelayout) {
- updateLeashPosition(mWin.getFrameNumber());
+ if (mWin.getWindowFrames().didFrameSizeChange() && mWin.mWinAnimator.getShown()
+ && mWin.okToDisplay()) {
+ mWin.applyWithNextDraw(mSetLeashPositionConsumer);
} else {
- mWin.mPendingPositionChanged = this;
+ mSetLeashPositionConsumer.accept(mWin.getPendingTransaction());
}
}
final Insets insetsHint = mSource.calculateInsets(
@@ -272,19 +280,6 @@
}
}
- void updateLeashPosition(long frameNumber) {
- if (mControl == null) {
- return;
- }
- final SurfaceControl leash = mControl.getLeash();
- if (leash != null) {
- final Transaction t = mDisplayContent.getPendingTransaction();
- final Point position = mControl.getSurfacePosition();
- t.setPosition(leash, position.x, position.y);
- deferTransactionUntil(t, leash, frameNumber);
- }
- }
-
private Point getWindowFrameSurfacePosition() {
final Rect frame = mWin.getFrame();
final Point position = new Point();
@@ -292,14 +287,6 @@
return position;
}
- private void deferTransactionUntil(Transaction t, SurfaceControl leash, long frameNumber) {
- if (frameNumber >= 0) {
- final SurfaceControl barrier = mWin.getClientViewRootSurface();
- t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
- t.deferTransactionUntil(leash, barrier, frameNumber);
- }
- }
-
/**
* @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
*/
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7c5afa8..7644cf2 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -43,7 +43,6 @@
import android.util.ArraySet;
import android.util.MathUtils;
import android.util.Slog;
-import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -296,9 +295,9 @@
}
boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
- final DisplayInfo displayInfo = wallpaperWin.getDisplayInfo();
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
+ final Rect parentFrame = wallpaperWin.getParentFrame();
+ final int dw = parentFrame.width();
+ final int dh = parentFrame.height();
int xOffset = 0;
int yOffset = 0;
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index 9245f8c..ffd6d21 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -113,7 +113,7 @@
}
/**
- * @return true if the width or height has changed since last reported to the client.
+ * @return true if the width or height has changed since last updating resizing window.
*/
boolean didFrameSizeChange() {
return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height());
@@ -135,6 +135,13 @@
}
/**
+ * @return true if the width or height has changed since last reported to the client.
+ */
+ boolean isFrameSizeChangeReported() {
+ return mFrameSizeChanged || didFrameSizeChange();
+ }
+
+ /**
* Resets the size changed flags so they're all set to false again. This should be called
* after the frames are reported to client.
*/
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a8ca5b6..d494b75 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2228,13 +2228,6 @@
win.setFrameNumber(frameNumber);
- final DisplayContent dc = win.getDisplayContent();
-
- if (win.mPendingPositionChanged != null) {
- win.mPendingPositionChanged.updateLeashPosition(frameNumber);
- win.mPendingPositionChanged = null;
- }
-
int attrChanges = 0;
int flagChanges = 0;
int privateFlagChanges = 0;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 48d4fc5..eb83152 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -726,8 +726,6 @@
*/
private InsetsState mFrozenInsetsState;
- @Nullable InsetsSourceProvider mPendingPositionChanged;
-
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
@@ -824,6 +822,12 @@
updateSurfacePosition(t);
};
+ private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
+ if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+ t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+ }
+ };
+
/**
* @see #setSurfaceTranslationY(int)
*/
@@ -2180,18 +2184,7 @@
final int left = mWindowFrames.mFrame.left;
final int top = mWindowFrames.mFrame.top;
- // During the transition from pip to fullscreen, the activity windowing mode is set to
- // fullscreen at the beginning while the task is kept in pinned mode. Skip the move
- // animation in such case since the transition is handled in SysUI.
- final boolean hasMovementAnimation = getTask() == null
- ? getWindowConfiguration().hasMovementAnimations()
- : getTask().getWindowConfiguration().hasMovementAnimations();
- if (mToken.okToAnimate()
- && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
- && !isDragResizing()
- && hasMovementAnimation
- && !mWinAnimator.mLastHidden
- && !mSeamlesslyRotated) {
+ if (canPlayMoveAnimation()) {
startMoveAnimation(left, top);
}
@@ -2207,6 +2200,22 @@
mMovedByResize = false;
}
+ private boolean canPlayMoveAnimation() {
+
+ // During the transition from pip to fullscreen, the activity windowing mode is set to
+ // fullscreen at the beginning while the task is kept in pinned mode. Skip the move
+ // animation in such case since the transition is handled in SysUI.
+ final boolean hasMovementAnimation = getTask() == null
+ ? getWindowConfiguration().hasMovementAnimations()
+ : getTask().getWindowConfiguration().hasMovementAnimations();
+ return mToken.okToAnimate()
+ && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+ && !isDragResizing()
+ && hasMovementAnimation
+ && !mWinAnimator.mLastHidden
+ && !mSeamlesslyRotated;
+ }
+
/**
* Return whether this window has moved. (Only makes
* sense to call from performLayoutAndPlaceSurfacesLockedInner().)
@@ -5377,13 +5386,18 @@
// prior to the rotation.
if (!mSurfaceAnimator.hasLeash() && mPendingSeamlessRotate == null
&& !mLastSurfacePosition.equals(mSurfacePosition)) {
- t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+ final boolean frameSizeChanged = mWindowFrames.isFrameSizeChangeReported();
+ final boolean surfaceInsetsChanged = surfaceInsetsChanging();
+ final boolean surfaceSizeChanged = frameSizeChanged || surfaceInsetsChanged;
mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
- if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) {
+ if (surfaceInsetsChanged) {
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
- t.deferTransactionUntil(mSurfaceControl,
- mWinAnimator.mSurfaceController.mSurfaceControl,
- getFrameNumber());
+ }
+ if (surfaceSizeChanged && mWinAnimator.getShown() && !canPlayMoveAnimation()
+ && okToDisplay()) {
+ applyWithNextDraw(mSetSurfacePositionConsumer);
+ } else {
+ mSetSurfacePositionConsumer.accept(t);
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index cdd5a92b..55ab8c3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -109,6 +109,14 @@
return 0;
}
+ @Override
+ public void acknowledgeDeviceCompliant() {}
+
+ @Override
+ public boolean isComplianceAcknowledgementRequired() {
+ return false;
+ }
+
public boolean canProfileOwnerResetPasswordWhenLocked(int userId) {
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 44791b0..8739a01 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -22,6 +22,7 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
@@ -687,12 +688,19 @@
private static final boolean ENABLE_LOCK_GUARD = true;
- /** Profile off deadline is not set or more than MANAGED_PROFILE_OFF_WARNING_PERIOD away. */
- private static final int PROFILE_OFF_DEADLINE_DEFAULT = 0;
- /** Profile off deadline is closer than MANAGED_PROFILE_OFF_WARNING_PERIOD. */
- private static final int PROFILE_OFF_DEADLINE_WARNING = 1;
- /** Profile off deadline reached, notify the user that personal apps blocked. */
- private static final int PROFILE_OFF_DEADLINE_REACHED = 2;
+ /**
+ * Profile off deadline is not set or more than MANAGED_PROFILE_OFF_WARNING_PERIOD away, or the
+ * user is running unlocked, no need for notification.
+ */
+ private static final int PROFILE_OFF_NOTIFICATION_NONE = 0;
+ /**
+ * Profile off deadline is closer than MANAGED_PROFILE_OFF_WARNING_PERIOD.
+ */
+ private static final int PROFILE_OFF_NOTIFICATION_WARNING = 1;
+ /**
+ * Profile off deadline reached, notify the user that personal apps blocked.
+ */
+ private static final int PROFILE_OFF_NOTIFICATION_SUSPENDED = 2;
interface Stats {
int LOCK_GUARD_GUARD = 0;
@@ -889,10 +897,9 @@
}
if (isManagedProfile(userHandle)) {
Slog.d(LOG_TAG, "Managed profile became unlocked");
- if (updatePersonalAppsSuspension(userHandle, true /* unlocked */)
- == PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT) {
- triggerPolicyComplianceCheck(userHandle);
- }
+ final boolean suspended =
+ updatePersonalAppsSuspension(userHandle, true /* unlocked */);
+ triggerPolicyComplianceCheckIfNeeded(userHandle, suspended);
}
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
handlePackagesChanged(null /* check all admins */, userHandle);
@@ -16111,7 +16118,6 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- // DO shouldn't be able to use this method.
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
Preconditions.checkState(canHandleCheckPolicyComplianceIntent(caller));
@@ -16142,18 +16148,26 @@
.write();
}
- /** Starts an activity to check policy compliance in the DPC. */
- private void triggerPolicyComplianceCheck(int profileUserId) {
- final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE);
+ /** Starts an activity to check policy compliance or request compliance acknowledgement. */
+ private void triggerPolicyComplianceCheckIfNeeded(int profileUserId, boolean suspended) {
synchronized (getLockObject()) {
final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
if (profileOwner == null) {
Slog.wtf(LOG_TAG, "Profile owner not found for compliance check");
return;
}
- intent.setPackage(profileOwner.info.getPackageName());
+ if (suspended) {
+ // If suspended, DPC will need to show an activity.
+ final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE);
+ intent.setPackage(profileOwner.info.getPackageName());
+ mContext.startActivityAsUser(intent, UserHandle.of(profileUserId));
+ } else if (profileOwner.mProfileOffDeadline > 0) {
+ // If not suspended, but deadline set, DPC needs to acknowledge compliance so that
+ // the deadline can be reset.
+ sendAdminCommandLocked(profileOwner, ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED,
+ /* adminExtras= */ null, /* receiver= */ null, /* inForeground = */ true);
+ }
}
- mContext.startActivityAsUser(intent, UserHandle.of(profileUserId));
}
/**
@@ -16162,39 +16176,35 @@
*
* @param unlocked whether the profile is currently running unlocked.
*/
- private @PersonalAppsSuspensionReason int updatePersonalAppsSuspension(
- int profileUserId, boolean unlocked) {
- final boolean suspendedExplicitly;
- final boolean suspendedByTimeout;
+ private boolean updatePersonalAppsSuspension(int profileUserId, boolean unlocked) {
+ final boolean shouldSuspend;
synchronized (getLockObject()) {
final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
if (profileOwner != null) {
- final int deadlineState =
- updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
- suspendedExplicitly = profileOwner.mSuspendPersonalApps;
- suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED;
- Slog.d(LOG_TAG, "Personal apps suspended explicitly: %b, deadline state: %d",
- suspendedExplicitly, deadlineState);
final int notificationState =
- unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState;
+ updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
+ final boolean suspendedExplicitly = profileOwner.mSuspendPersonalApps;
+ final boolean suspendedByTimeout = profileOwner.mProfileOffDeadline == -1;
+ Slog.d(LOG_TAG,
+ "Personal apps suspended explicitly: %b, by timeout: %b, notification: %d",
+ suspendedExplicitly, suspendedByTimeout, notificationState);
updateProfileOffDeadlineNotificationLocked(
profileUserId, profileOwner, notificationState);
+ shouldSuspend = suspendedExplicitly || suspendedByTimeout;
} else {
- suspendedExplicitly = false;
- suspendedByTimeout = false;
+ shouldSuspend = false;
}
}
final int parentUserId = getProfileParentId(profileUserId);
- suspendPersonalAppsInternal(parentUserId, suspendedExplicitly || suspendedByTimeout);
-
- return makeSuspensionReasons(suspendedExplicitly, suspendedByTimeout);
+ suspendPersonalAppsInternal(parentUserId, shouldSuspend);
+ return shouldSuspend;
}
/**
* Checks work profile time off policy, scheduling personal apps suspension via alarm if
* necessary.
- * @return profile deadline state
+ * @return notification state
*/
private int updateProfileOffDeadlineLocked(
int profileUserId, ActiveAdmin profileOwner, boolean unlocked) {
@@ -16206,7 +16216,7 @@
profileOwner.mProfileOffDeadline = -1;
saveSettingsLocked(profileUserId);
}
- return PROFILE_OFF_DEADLINE_REACHED;
+ return unlocked ? PROFILE_OFF_NOTIFICATION_NONE : PROFILE_OFF_NOTIFICATION_SUSPENDED;
}
boolean shouldSaveSettings = false;
if (profileOwner.mSuspendPersonalApps) {
@@ -16216,8 +16226,8 @@
shouldSaveSettings = true;
}
} else if (profileOwner.mProfileOffDeadline != 0
- && (profileOwner.mProfileMaximumTimeOffMillis == 0 || unlocked)) {
- // There is a deadline but either there is no policy or the profile is unlocked -> clear
+ && (profileOwner.mProfileMaximumTimeOffMillis == 0)) {
+ // There is a deadline but either there is no policy -> clear
// the deadline.
Slog.i(LOG_TAG, "Profile off deadline is reset to zero");
profileOwner.mProfileOffDeadline = 0;
@@ -16236,19 +16246,19 @@
}
final long alarmTime;
- final int deadlineState;
- if (profileOwner.mProfileOffDeadline == 0) {
+ final int notificationState;
+ if (unlocked || profileOwner.mProfileOffDeadline == 0) {
alarmTime = 0;
- deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+ notificationState = PROFILE_OFF_NOTIFICATION_NONE;
} else if (profileOwner.mProfileOffDeadline - now < MANAGED_PROFILE_OFF_WARNING_PERIOD) {
// The deadline is close, upon the alarm personal apps should be suspended.
alarmTime = profileOwner.mProfileOffDeadline;
- deadlineState = PROFILE_OFF_DEADLINE_WARNING;
+ notificationState = PROFILE_OFF_NOTIFICATION_WARNING;
} else {
// The deadline is quite far, upon the alarm we should warn the user first, so the
// alarm is scheduled earlier than the actual deadline.
alarmTime = profileOwner.mProfileOffDeadline - MANAGED_PROFILE_OFF_WARNING_PERIOD;
- deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+ notificationState = PROFILE_OFF_NOTIFICATION_NONE;
}
final AlarmManager am = mInjector.getAlarmManager();
@@ -16268,7 +16278,7 @@
am.set(AlarmManager.RTC, alarmTime, pi);
}
- return deadlineState;
+ return notificationState;
}
private void suspendPersonalAppsInternal(int userId, boolean suspended) {
@@ -16310,7 +16320,7 @@
@GuardedBy("getLockObject()")
private void updateProfileOffDeadlineNotificationLocked(
int profileUserId, ActiveAdmin profileOwner, int notificationState) {
- if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) {
+ if (notificationState == PROFILE_OFF_NOTIFICATION_NONE) {
mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED);
return;
}
@@ -16331,7 +16341,7 @@
final String text;
final boolean ongoing;
- if (notificationState == PROFILE_OFF_DEADLINE_WARNING) {
+ if (notificationState == PROFILE_OFF_NOTIFICATION_WARNING) {
// Round to the closest integer number of days.
final int maxDays = (int)
((profileOwner.mProfileMaximumTimeOffMillis + MS_PER_DAY / 2) / MS_PER_DAY);
@@ -16422,7 +16432,6 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- // DO shouldn't be able to use this method.
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
synchronized (getLockObject()) {
@@ -16432,6 +16441,33 @@
}
@Override
+ public void acknowledgeDeviceCompliant() {
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
+ enforceUserUnlocked(caller.getUserId());
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getProfileOwnerLocked(caller);
+ if (admin.mProfileOffDeadline > 0) {
+ admin.mProfileOffDeadline = 0;
+ saveSettingsLocked(caller.getUserId());
+ }
+ }
+ }
+
+ @Override
+ public boolean isComplianceAcknowledgementRequired() {
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
+ enforceUserUnlocked(caller.getUserId());
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getProfileOwnerLocked(caller);
+ return admin.mProfileOffDeadline != 0;
+ }
+ }
+
+ @Override
public boolean canProfileOwnerResetPasswordWhenLocked(int userId) {
enforceSystemCaller("call canProfileOwnerResetPasswordWhenLocked");
synchronized (getLockObject()) {
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index a262939..29aedce 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -295,10 +295,30 @@
return;
}
- try {
- mIProfcollect.report();
- } catch (RemoteException e) {
- Log.e(LOG_TAG, e.getMessage());
- }
+ final boolean uploadReport =
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+ "upload_report", false);
+
+ new Thread(() -> {
+ try {
+ String reportPath = mIProfcollect.report();
+ if (!uploadReport) {
+ return;
+ }
+ Intent uploadIntent =
+ new Intent("com.google.android.apps.betterbug.intent.action.UPLOAD_PROFILE")
+ .setPackage("com.google.android.apps.internal.betterbug")
+ .putExtra("EXTRA_DESTINATION", "PROFCOLLECT")
+ .putExtra("EXTRA_PACKAGE_NAME", getContext().getPackageName())
+ .putExtra("EXTRA_PROFILE_PATH", reportPath)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ Context context = getContext();
+ if (context.getPackageManager().queryBroadcastReceivers(uploadIntent, 0) != null) {
+ context.sendBroadcast(uploadIntent);
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, e.getMessage());
+ }
+ }).start();
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 1b0a305..537a49e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -417,7 +417,7 @@
allowQueryAll.set(true)
- runMethod(target, NON_VERIFIER_UID)
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
}
private fun approvedVerifier() {
@@ -816,7 +816,7 @@
// System/shell only
INTERNAL,
- // INTERNAL || domain verification agent || user setting permission holder
+ // INTERNAL || non-legacy domain verification agent
QUERENT,
// INTERNAL || domain verification agent
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 28940b3..8481961 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -21,6 +21,7 @@
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
+import static android.app.AlarmManager.FLAG_PRIORITIZE;
import static android.app.AlarmManager.FLAG_STANDALONE;
import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.RTC;
@@ -62,6 +63,7 @@
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -443,6 +445,12 @@
TEST_CALLING_UID);
}
+ private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) {
+ mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
+ FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE, null);
+ }
+
private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi,
boolean unrestricted) {
final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE;
@@ -579,6 +587,7 @@
setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 40);
setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 45);
setDeviceConfigLong(KEY_MIN_WINDOW, 50);
+ setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, 55);
assertEquals(5, mService.mConstants.MIN_FUTURITY);
assertEquals(10, mService.mConstants.MIN_INTERVAL);
assertEquals(15, mService.mConstants.MAX_INTERVAL);
@@ -589,6 +598,7 @@
assertEquals(40, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
assertEquals(45, mService.mConstants.LISTENER_TIMEOUT);
assertEquals(50, mService.mConstants.MIN_WINDOW);
+ assertEquals(55, mService.mConstants.PRIORITY_ALARM_DELAY);
}
@Test
@@ -1586,6 +1596,89 @@
}
@Test
+ public void prioritizedAlarmsInBatterySaver() throws Exception {
+ when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(true);
+ when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
+ final long minDelay = 5;
+ setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay);
+
+ final long firstTrigger = mNowElapsedTest + 4;
+ final AtomicInteger alarmsFired = new AtomicInteger(0);
+ final int numAlarms = 10;
+ for (int i = 0; i < numAlarms; i++) {
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+ new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback)
+ throws RemoteException {
+ alarmsFired.incrementAndGet();
+ }
+ });
+ }
+ assertEquals(firstTrigger, mTestTimer.getElapsed());
+ mNowElapsedTest = firstTrigger;
+ mTestTimer.expire();
+ while (alarmsFired.get() < numAlarms) {
+ assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ assertEquals(numAlarms, alarmsFired.get());
+ }
+
+ @Test
+ public void prioritizedAlarmsInDeviceIdle() throws Exception {
+ doReturn(0).when(mService).fuzzForDuration(anyLong());
+
+ final long minDelay = 5;
+ setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay);
+
+ final long idleUntil = mNowElapsedTest + 1000;
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil, getNewMockPendingIntent());
+ assertNotNull(mService.mPendingIdleUntil);
+
+ final long firstTrigger = mNowElapsedTest + 4;
+ final AtomicInteger alarmsFired = new AtomicInteger(0);
+ final int numAlarms = 10;
+ for (int i = 0; i < numAlarms; i++) {
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+ new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback)
+ throws RemoteException {
+ alarmsFired.incrementAndGet();
+ }
+ });
+ }
+ assertEquals(firstTrigger, mTestTimer.getElapsed());
+ mNowElapsedTest = firstTrigger;
+ mTestTimer.expire();
+ while (alarmsFired.get() < numAlarms) {
+ assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ assertEquals(numAlarms, alarmsFired.get());
+
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ }
+ });
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ }
+ });
+ assertEquals(idleUntil - 3, mTestTimer.getElapsed());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+
+ assertEquals(idleUntil, mTestTimer.getElapsed());
+ }
+
+ @Test
public void dispatchOrder() throws Exception {
doReturn(0).when(mService).fuzzForDuration(anyLong());
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5761958..9513c6e 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -70,6 +70,7 @@
<uses-permission android:name="android.permission.CONTROL_KEYGUARD"/>
<uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/>
<uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index 73a2feb..4a67ec7 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -20,6 +20,7 @@
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_BT;
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO;
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI;
import static org.junit.Assert.assertArrayEquals;
@@ -93,6 +94,16 @@
tempAllIds.add(btId);
mPowerStatsInternal.incrementEnergyConsumption(btId, 34567);
+ final int gnssId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.GNSS, 0,
+ "gnss");
+ tempAllIds.add(gnssId);
+ mPowerStatsInternal.incrementEnergyConsumption(gnssId, 787878);
+
+ final int mobileRadioId = mPowerStatsInternal.addEnergyConsumer(
+ EnergyConsumerType.MOBILE_RADIO, 0, "mobile_radio");
+ tempAllIds.add(mobileRadioId);
+ mPowerStatsInternal.incrementEnergyConsumption(mobileRadioId, 62626);
+
final int[] cpuClusterIds = new int[numCpuClusters];
for (int i = 0; i < numCpuClusters; i++) {
cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer(
@@ -135,6 +146,12 @@
assertEquals(1, bluetoothResults.length);
assertEquals(btId, bluetoothResults[0].id);
+ final EnergyConsumerResult[] mobileRadioResults =
+ mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_RADIO).getNow(null);
+ // Results should only have the mobile radio energy consumer
+ assertEquals(1, mobileRadioResults.length);
+ assertEquals(mobileRadioId, mobileRadioResults[0].id);
+
final EnergyConsumerResult[] cpuResults =
mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null);
// Results should only have the cpu cluster energy consumers
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 9ffb5017..5c8a7d2 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -80,6 +80,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
@@ -123,6 +124,7 @@
private static final long HANDLER_WAIT_TIME_MS = 100;
private UserController mUserController;
+ private LockPatternUtils mLockPatternUtils;
private TestInjector mInjector;
private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
@@ -161,6 +163,13 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
+
+ // Make it appear that calling unlockUserKey() is needed.
+ doReturn(true).when(mInjector).isFileEncryptedNativeOnly();
+ mLockPatternUtils = mock(LockPatternUtils.class);
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false);
+ doReturn(mLockPatternUtils).when(mInjector).getLockPatternUtils();
+
// All UserController params are set to default.
mUserController = new UserController(mInjector);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -552,6 +561,20 @@
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
}
+ /**
+ * Test that if a user has a lock screen credential set, then UserController
+ * doesn't bother trying to unlock their storage key without a credential
+ * token, as it will never work.
+ */
+ @Test
+ public void testSecureUserUnlockNotAttempted() throws Exception {
+ when(mLockPatternUtils.isSecure(eq(TEST_USER_ID1))).thenReturn(true);
+ setUpUser(TEST_USER_ID1, 0);
+ mUserController.startUser(TEST_USER_ID1, /* foreground= */ false);
+ verify(mInjector.mStorageManagerMock, times(0))
+ .unlockUserKey(eq(TEST_USER_ID1), anyInt(), any(), any());
+ }
+
@Test
public void testStartProfile_fullUserFails() {
setUpUser(TEST_USER_ID1, 0);
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
index f3ee233..4240581 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -104,12 +104,12 @@
// Insert package1 document
GenericDocument document1 =
- new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
+ new GenericDocument.Builder<>("namespace", "uri", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
// Insert package2 document
GenericDocument document2 =
- new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
+ new GenericDocument.Builder<>("namespace", "uri", "schema2").build();
mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null);
// No query filters specified, global query can retrieve all documents.
@@ -122,8 +122,8 @@
// Document2 will be first since it got indexed later and has a "better", aka more recent
// score.
- assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2);
- assertThat(searchResultPage.getResults().get(1).getDocument()).isEqualTo(document1);
+ assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+ assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(document1);
}
/**
@@ -158,12 +158,12 @@
// Insert package1 document
GenericDocument document1 =
- new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
+ new GenericDocument.Builder<>("namespace", "uri", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
// Insert package2 document
GenericDocument document2 =
- new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
+ new GenericDocument.Builder<>("namespace", "uri", "schema2").build();
mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null);
// "package1" filter specified
@@ -176,7 +176,7 @@
mAppSearchImpl.globalQuery(
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid);
assertThat(searchResultPage.getResults()).hasSize(1);
- assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document1);
+ assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
// "package2" filter specified
searchSpec =
@@ -188,7 +188,7 @@
mAppSearchImpl.globalQuery(
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid);
assertThat(searchResultPage.getResults()).hasSize(1);
- assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2);
+ assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
index fcd6b84..7bdc87e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
@@ -116,7 +116,7 @@
}
CompatConfigBuilder addOverridableChangeWithId(long id) {
- mChanges.add(new CompatChange(id, "", -1, -1, false, true, "", true));
+ mChanges.add(new CompatChange(id, "", -1, -1, true, false, "", true));
return this;
}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index bd77405..a866363 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -260,6 +260,36 @@
}
@Test
+ public void testInstallerCanSetOverrides() throws Exception {
+ final long changeId = 1234L;
+ final int installerUid = 23;
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addOverridableChangeWithId(1234L)
+ .build();
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package")
+ .build();
+ PackageManager packageManager = mock(PackageManager.class);
+ when(mContext.getPackageManager()).thenReturn(packageManager);
+ when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(applicationInfo);
+
+ // Force the validator to prevent overriding the change by using a user build.
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+ CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
+ Collections.singletonMap(1234L,
+ new PackageOverride.Builder()
+ .setMaxVersionCode(99L)
+ .setEnabled(true)
+ .build()));
+
+ compatConfig.addOverrides(config, "com.some.package");
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+ }
+
+ @Test
public void testApplyDeferredOverridesAfterInstallingApp() throws Exception {
ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
.withPackageName("com.notinstalled.foo")
@@ -639,9 +669,18 @@
.build());
when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
.thenThrow(new NameNotFoundException());
-
- compatConfig.addOverride(1L, "foo.bar", true);
- compatConfig.addOverride(2L, "bar.baz", false);
+ compatConfig.addOverrides(
+ new CompatibilityOverrideConfig(
+ Collections.singletonMap(
+ 1L,
+ new PackageOverride.Builder().setEnabled(true).build())),
+ "foo.bar");
+ compatConfig.addOverrides(
+ new CompatibilityOverrideConfig(
+ Collections.singletonMap(
+ 2L,
+ new PackageOverride.Builder().setEnabled(false).build())),
+ "bar.baz");
assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<overrides>\n"
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 3fc6e99..a2664e5 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -113,7 +113,7 @@
new CompatibilityChangeInfo(
6L, "", Build.VERSION_CODES.R, -1, false, false, "", false),
new CompatibilityChangeInfo(7L, "", -1, -1, false, true, "", false),
- new CompatibilityChangeInfo(8L, "", -1, -1, false, true, "", true));
+ new CompatibilityChangeInfo(8L, "", -1, -1, true, false, "", true));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 15ada89..c8099e2 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -464,6 +464,40 @@
}
@Test
+ public void testStaleAppRequestSize() {
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector);
+ Display.Mode[] modes = new Display.Mode[] {
+ new Display.Mode(1, 1280, 720, 60),
+ };
+ Display.Mode defaultMode = modes[0];
+
+ // Inject supported modes
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Inject default mode
+ SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
+ defaultModesByDisplay.put(DISPLAY_ID, defaultMode);
+ director.injectDefaultModeByDisplay(defaultModesByDisplay);
+
+ // Inject votes
+ SparseArray<Vote> votes = new SparseArray<>();
+ votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(1920, 1080));
+ votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(50, 50));
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+ votesByDisplay.put(DISPLAY_ID, votes);
+ director.injectVotesByDisplay(votesByDisplay);
+
+ director.setShouldAlwaysRespectAppRequestedMode(true);
+
+ // We should return the only available mode
+ DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+ assertThat(specs.baseModeId).isEqualTo(defaultMode.getModeId());
+ }
+
+ @Test
public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index eebc25a..6f1268e 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -34,6 +35,7 @@
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
+import android.util.ArrayMap;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -48,6 +50,7 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -793,6 +796,97 @@
}
}
+ @Test
+ public void testChooseSystemVoiceIme() throws Exception {
+ final InputMethodInfo systemIme = createFakeInputMethodInfo("SystemIme", "fake.voice0",
+ true /* isSystem */);
+
+ // Returns null when the config value is null.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, null, ""));
+ }
+
+ // Returns null when the config value is empty.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, "", ""));
+ }
+
+ // Returns null when the configured package doesn't have an IME.
+ {
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(),
+ systemIme.getPackageName(), ""));
+ }
+
+ // Returns the right one when the current default is null.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ systemIme.getPackageName(), null));
+ }
+
+ // Returns the right one when the current default is empty.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ systemIme.getPackageName(), ""));
+ }
+
+ // Returns null when the current default isn't found.
+ {
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(),
+ systemIme.getPackageName(), systemIme.getId()));
+ }
+
+ // Returns null when there are multiple IMEs defined by the config package.
+ {
+ final InputMethodInfo secondIme = createFakeInputMethodInfo(systemIme.getPackageName(),
+ "fake.voice1", true /* isSystem */);
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ methodMap.put(secondIme.getId(), secondIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(),
+ ""));
+ }
+
+ // Returns the current one when the current default and config point to the same package.
+ {
+ final InputMethodInfo secondIme = createFakeInputMethodInfo("SystemIme", "fake.voice1",
+ true /* isSystem */);
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ methodMap.put(secondIme.getId(), secondIme);
+ assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ systemIme.getPackageName(), systemIme.getId()));
+ }
+
+ // Doesn't return the current default if it isn't a system app.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
+ "fake.voice0", false /* isSystem */);
+ methodMap.put(nonSystemIme.getId(), nonSystemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ nonSystemIme.getPackageName(), nonSystemIme.getId()));
+ }
+
+ // Returns null if the configured one isn't a system app.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final InputMethodInfo nonSystemIme = createFakeInputMethodInfo(
+ "FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */);
+ methodMap.put(systemIme.getId(), systemIme);
+ methodMap.put(nonSystemIme.getId(), nonSystemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ nonSystemIme.getPackageName(), ""));
+ }
+ }
+
private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
final Locale systemLocale, String... expectedImeNames) {
final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
@@ -866,6 +960,25 @@
}
private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
+ boolean isSystem) {
+ final ResolveInfo ri = new ResolveInfo();
+ final ServiceInfo si = new ServiceInfo();
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.enabled = true;
+ if (isSystem) {
+ ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+ }
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = packageName;
+ si.name = name;
+ si.exported = true;
+ ri.serviceInfo = si;
+ return new InputMethodInfo(ri, false, "", Collections.emptyList(), 1, true);
+ }
+
+ private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
CharSequence label, boolean isAuxIme, boolean isDefault,
List<InputMethodSubtype> subtypes) {
final ResolveInfo ri = new ResolveInfo();
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index d405113..100d3ea 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -1773,57 +1773,75 @@
true);
}
- /**
- * Test that when StatsProvider triggers limit reached, new limit will be calculated and
- * re-armed.
- */
- @Test
- public void testStatsProviderLimitReached() throws Exception {
- final int CYCLE_DAY = 15;
-
- final NetworkStats stats = new NetworkStats(0L, 1);
+ private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) {
stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
- 2999, 1, 2000, 1, 0);
+ rxBytes, 1, txBytes, 1, 0);
when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
.thenReturn(stats.getTotalBytes());
when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
.thenReturn(stats);
+ }
+
+ private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException {
+ final NetworkPolicyManagerInternal npmi = LocalServices
+ .getService(NetworkPolicyManagerInternal.class);
+ npmi.onStatsProviderWarningOrLimitReached("TEST");
+ // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED.
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).forceUpdate();
+ // Wait for processing of MSG_*_INTERFACE_QUOTAS.
+ postMsgAndWaitForCompletion();
+ }
+
+ /**
+ * Test that when StatsProvider triggers warning and limit reached, new quotas will be
+ * calculated and re-armed.
+ */
+ @Test
+ public void testStatsProviderWarningAndLimitReached() throws Exception {
+ final int CYCLE_DAY = 15;
+
+ final NetworkStats stats = new NetworkStats(0L, 1);
+ increaseMockedTotalBytes(stats, 2999, 2000);
// Get active mobile network in place
expectMobileDefaults();
mService.updateNetworks();
- verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE);
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE,
+ Long.MAX_VALUE);
- // Set limit to 10KB.
+ // Set warning to 7KB and limit to 10KB.
setNetworkPolicies(new NetworkPolicy(
- sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
- true));
+ sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true));
postMsgAndWaitForCompletion();
- // Verifies that remaining quota is set to providers.
- verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L);
-
+ // Verifies that remaining quotas are set to providers.
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L);
reset(mStatsService);
- // Increase the usage.
- stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
- 1000, 1, 999, 1, 0);
- when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
- .thenReturn(stats.getTotalBytes());
- when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
- .thenReturn(stats);
+ // Increase the usage and simulates that limit reached fires earlier by provider,
+ // but actually the quota is not yet reached. Verifies that the limit reached leads to
+ // a force update and new quotas should be set.
+ increaseMockedTotalBytes(stats, 1000, 999);
+ triggerOnStatsProviderWarningOrLimitReached();
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L);
+ reset(mStatsService);
- // Simulates that limit reached fires earlier by provider, but actually the quota is not
- // yet reached.
- final NetworkPolicyManagerInternal npmi = LocalServices
- .getService(NetworkPolicyManagerInternal.class);
- npmi.onStatsProviderWarningOrLimitReached("TEST");
+ // Increase the usage and simulate warning reached, the new warning should be unlimited
+ // since service will disable warning quota to stop lower layer from keep triggering
+ // warning reached event.
+ increaseMockedTotalBytes(stats, 1000L, 1000);
+ triggerOnStatsProviderWarningOrLimitReached();
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(
+ TEST_IFACE, Long.MAX_VALUE, 1002L);
+ reset(mStatsService);
- // Verifies that the limit reached leads to a force update and new limit should be set.
- postMsgAndWaitForCompletion();
- verify(mStatsService).forceUpdate();
- postMsgAndWaitForCompletion();
- verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L);
+ // Increase the usage that over the warning and limit, the new limit should set to 1 to
+ // block the network traffic.
+ increaseMockedTotalBytes(stats, 1000L, 1000);
+ triggerOnStatsProviderWarningOrLimitReached();
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L);
+ reset(mStatsService);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 212a9c6..8e2b207 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -89,6 +89,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -769,6 +770,21 @@
}
@Override
+ public void writeQueryResultsToFile(String packageName, String databaseName,
+ ParcelFileDescriptor fileDescriptor, String queryExpression,
+ Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void putDocumentsFromFile(String packageName, String databaseName,
+ ParcelFileDescriptor fileDescriptor, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
public void reportUsage(String packageName, String databaseName, String namespace,
String uri, long usageTimeMillis, boolean systemUsage, int userId,
IAppSearchResultCallback callback)
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 d63a467..a231169 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -287,7 +287,7 @@
final SuspendDialogInfo dialogInfo1 = new SuspendDialogInfo.Builder()
.setIcon(0x11220001)
- .setTitle(0x11220002)
+ .setTitle("String Title")
.setMessage("1st message")
.setNeutralButtonText(0x11220003)
.setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
@@ -296,7 +296,7 @@
.setIcon(0x22220001)
.setTitle(0x22220002)
.setMessage("2nd message")
- .setNeutralButtonText(0x22220003)
+ .setNeutralButtonText("String button text")
.setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
.build();
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java
index 322e448..826a8d4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java
@@ -57,7 +57,7 @@
}
@Test
- public void equalsComparesTitle() {
+ public void equalsComparesTitleIds() {
final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder();
final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder();
assertEquals(dialogBuilder1.build(), dialogBuilder2.build());
@@ -67,7 +67,39 @@
}
@Test
- public void equalsComparesButtonText() {
+ public void equalsIgnoresTitleStringsWhenIdsSet() {
+ final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder()
+ .setTitle(VALID_TEST_RES_ID_1)
+ .setTitle("1st title");
+ final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder()
+ .setTitle(VALID_TEST_RES_ID_1)
+ .setTitle("2nd title");
+ // String titles different but should get be ignored when resource ids are set
+ assertEquals(dialogBuilder1.build(), dialogBuilder2.build());
+ }
+
+ @Test
+ public void equalsComparesTitleStringsWhenNoIdsSet() {
+ final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder()
+ .setTitle("1st title");
+ final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder()
+ .setTitle("2nd title");
+ // Both have different titles, which are not ignored as resource ids aren't set
+ assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build());
+ }
+
+ @Test
+ public void titleStringClearedWhenResIdSet() {
+ final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder()
+ .setTitle(VALID_TEST_RES_ID_2)
+ .setTitle("Should be cleared on build")
+ .build();
+ assertNull(dialogInfo.getTitle());
+ assertEquals(VALID_TEST_RES_ID_2, dialogInfo.getTitleResId());
+ }
+
+ @Test
+ public void equalsComparesButtonTextIds() {
final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder();
final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder();
assertEquals(dialogBuilder1.build(), dialogBuilder2.build());
@@ -77,6 +109,38 @@
}
@Test
+ public void equalsIgnoresButtonStringsWhenIdsSet() {
+ final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder()
+ .setNeutralButtonText(VALID_TEST_RES_ID_1)
+ .setNeutralButtonText("1st button text");
+ final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder()
+ .setNeutralButtonText(VALID_TEST_RES_ID_1)
+ .setNeutralButtonText("2nd button text");
+ // Button strings different but should get be ignored when resource ids are set
+ assertEquals(dialogBuilder1.build(), dialogBuilder2.build());
+ }
+
+ @Test
+ public void equalsComparesButtonStringsWhenNoIdsSet() {
+ final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder()
+ .setNeutralButtonText("1st button text");
+ final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder()
+ .setNeutralButtonText("2nd button text");
+ // Both have different button texts, which are not ignored as resource ids aren't set
+ assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build());
+ }
+
+ @Test
+ public void buttonStringClearedWhenResIdSet() {
+ final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder()
+ .setNeutralButtonText(VALID_TEST_RES_ID_2)
+ .setNeutralButtonText("Should be cleared on build")
+ .build();
+ assertNull(dialogInfo.getNeutralButtonText());
+ assertEquals(VALID_TEST_RES_ID_2, dialogInfo.getNeutralButtonTextResId());
+ }
+
+ @Test
public void equalsComparesButtonAction() {
final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder();
final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder();
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 6e5fbd0..2df6c5a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -71,6 +71,7 @@
import android.service.dreams.DreamManagerInternal;
import android.test.mock.MockContentResolver;
import android.view.Display;
+import android.view.DisplayInfo;
import androidx.test.InstrumentationRegistry;
@@ -594,6 +595,8 @@
public void testWasDeviceIdleFor_true() {
int interval = 1000;
createService();
+ mService.systemReady(null);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mService.onUserActivity();
advanceTime(interval + 1 /* just a little more */);
assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
@@ -603,6 +606,8 @@
public void testWasDeviceIdleFor_false() {
int interval = 1000;
createService();
+ mService.systemReady(null);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mService.onUserActivity();
assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
}
@@ -757,6 +762,9 @@
@Test
public void testInattentiveSleep_userActivityDismissesWarning() throws Exception {
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
setMinimumScreenOffTimeoutConfig(5);
setAttentiveWarningDuration(1900);
setAttentiveTimeout(2000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
deleted file mode 100644
index 1700707..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ /dev/null
@@ -1,346 +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 static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
-import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link DisplayContent} class.
- *
- * Build/Install/Run:
- * atest WmTests:ActivityDisplayTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-// TODO(b/144248496): Merge to DisplayContentTests
-public class ActivityDisplayTests extends WindowTestsBase {
-
- @Test
- public void testLastFocusedStackIsUpdatedWhenMovingStack() {
- // Create a stack at bottom.
- final TaskDisplayArea taskDisplayAreas =
- mRootWindowContainer.getDefaultDisplay().getDefaultTaskDisplayArea();
- final Task stack =
- new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build();
- final Task prevFocusedStack = taskDisplayAreas.getFocusedRootTask();
-
- stack.moveToFront("moveStackToFront");
- // After moving the stack to front, the previous focused should be the last focused.
- assertTrue(stack.isFocusedRootTaskOnDisplay());
- assertEquals(prevFocusedStack, taskDisplayAreas.getLastFocusedRootTask());
-
- stack.moveToBack("moveStackToBack", null /* task */);
- // After moving the stack to back, the stack should be the last focused.
- assertEquals(stack, taskDisplayAreas.getLastFocusedRootTask());
- }
-
- /**
- * This test simulates the picture-in-picture menu activity launches an activity to fullscreen
- * stack. The fullscreen stack should be the top focused for resuming correctly.
- */
- @Test
- public void testFullscreenStackCanBeFocusedWhenFocusablePinnedStackExists() {
- // Create a pinned stack and move to front.
- final Task pinnedStack = mRootWindowContainer.getDefaultTaskDisplayArea()
- .createRootTask(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
- final Task pinnedTask = new TaskBuilder(mAtm.mTaskSupervisor)
- .setParentTask(pinnedStack).build();
- new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
- .setTask(pinnedTask).build();
- pinnedStack.moveToFront("movePinnedStackToFront");
-
- // The focused stack should be the pinned stack.
- assertTrue(pinnedStack.isFocusedRootTaskOnDisplay());
-
- // Create a fullscreen stack and move to front.
- final Task fullscreenStack = createFullscreenStackWithSimpleActivityAt(
- mRootWindowContainer.getDefaultDisplay());
- fullscreenStack.moveToFront("moveFullscreenStackToFront");
-
- // The focused stack should be the fullscreen stack.
- assertTrue(fullscreenStack.isFocusedRootTaskOnDisplay());
- }
-
- /**
- * Test {@link TaskDisplayArea#mPreferredTopFocusableRootTask} will be cleared when
- * the stack is removed or moved to back, and the focused stack will be according to z-order.
- */
- @Test
- public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() {
- // Create a display which only contains 2 stacks.
- final DisplayContent display = addNewDisplayContentAt(POSITION_TOP);
- final Task stack1 = createFullscreenStackWithSimpleActivityAt(display);
- final Task stack2 = createFullscreenStackWithSimpleActivityAt(display);
-
- // Put stack1 and stack2 on top.
- stack1.moveToFront("moveStack1ToFront");
- stack2.moveToFront("moveStack2ToFront");
- assertTrue(stack2.isFocusedRootTaskOnDisplay());
-
- // Stack1 should be focused after moving stack2 to back.
- stack2.moveToBack("moveStack2ToBack", null /* task */);
- assertTrue(stack1.isFocusedRootTaskOnDisplay());
-
- // Stack2 should be focused after removing stack1.
- stack1.getDisplayArea().removeRootTask(stack1);
- assertTrue(stack2.isFocusedRootTaskOnDisplay());
- }
-
- /**
- * Verifies {@link DisplayContent#remove} should not resume home stack on the removing display.
- */
- @Test
- public void testNotResumeHomeStackOnRemovingDisplay() {
- // Create a display which supports system decoration and allows reparenting stacks to
- // another display when the display is removed.
- final DisplayContent display = new TestDisplayContent.Builder(
- mAtm, 1000, 1500).setSystemDecorations(true).build();
- doReturn(false).when(display).shouldDestroyContentOnRemove();
-
- // Put home stack on the display.
- final Task homeStack = new TaskBuilder(mSupervisor)
- .setDisplay(display).setActivityType(ACTIVITY_TYPE_HOME).build();
-
- // Put a finishing standard activity which will be reparented.
- final Task stack = createFullscreenStackWithSimpleActivityAt(display);
- stack.topRunningActivity().makeFinishingLocked();
-
- clearInvocations(homeStack);
- display.remove();
-
- // The removed display should have no focused stack and its home stack should never resume.
- assertNull(display.getFocusedRootTask());
- verify(homeStack, never()).resumeTopActivityUncheckedLocked(any(), any());
- }
-
- private Task createFullscreenStackWithSimpleActivityAt(DisplayContent display) {
- final Task fullscreenStack = display.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);
- final Task fullscreenTask = new TaskBuilder(mAtm.mTaskSupervisor)
- .setParentTask(fullscreenStack).build();
- new ActivityBuilder(mAtm).setTask(fullscreenTask).build();
- return fullscreenStack;
- }
-
- /**
- * Verifies the correct activity is returned when querying the top running activity.
- */
- @Test
- public void testTopRunningActivity() {
- final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
- final KeyguardController keyguard = mSupervisor.getKeyguardController();
- final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
- final ActivityRecord activity = stack.getTopNonFinishingActivity();
-
- // Create empty stack on top.
- final Task emptyStack = new TaskBuilder(mSupervisor).build();
-
- // Make sure the top running activity is not affected when keyguard is not locked.
- assertTopRunningActivity(activity, display);
-
- // Check to make sure activity not reported when it cannot show on lock and lock is on.
- doReturn(true).when(keyguard).isKeyguardLocked();
- assertEquals(activity, display.topRunningActivity());
- assertNull(display.topRunningActivity(true /* considerKeyguardState */));
-
- // Move stack with activity to top.
- stack.moveToFront("testStackToFront");
- assertEquals(stack, display.getFocusedRootTask());
- assertEquals(activity, display.topRunningActivity());
- assertNull(display.topRunningActivity(true /* considerKeyguardState */));
-
- // Add activity that should be shown on the keyguard.
- final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mAtm)
- .setTask(stack)
- .setActivityFlags(FLAG_SHOW_WHEN_LOCKED)
- .build();
-
- // Ensure the show when locked activity is returned.
- assertTopRunningActivity(showWhenLockedActivity, display);
-
- // Move empty stack to front. The running activity in focusable stack which below the
- // empty stack should be returned.
- emptyStack.moveToFront("emptyStackToFront");
- assertEquals(stack, display.getFocusedRootTask());
- assertTopRunningActivity(showWhenLockedActivity, display);
- }
-
- private static void assertTopRunningActivity(ActivityRecord top, DisplayContent display) {
- assertEquals(top, display.topRunningActivity());
- assertEquals(top, display.topRunningActivity(true /* considerKeyguardState */));
- }
-
- /**
- * This test enforces that alwaysOnTop stack is placed at proper position.
- */
- @Test
- public void testAlwaysOnTopStackLocation() {
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- final Task alwaysOnTopStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM,
- ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setTask(alwaysOnTopStack).build();
- alwaysOnTopStack.setAlwaysOnTop(true);
- taskDisplayArea.positionChildAt(POSITION_TOP, alwaysOnTopStack,
- false /* includingParents */);
- assertTrue(alwaysOnTopStack.isAlwaysOnTop());
- // Ensure always on top state is synced to the children of the stack.
- assertTrue(alwaysOnTopStack.getTopNonFinishingActivity().isAlwaysOnTop());
- assertEquals(alwaysOnTopStack, taskDisplayArea.getTopRootTask());
-
- final Task pinnedStack = taskDisplayArea.createRootTask(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- assertEquals(pinnedStack, taskDisplayArea.getRootPinnedTask());
- assertEquals(pinnedStack, taskDisplayArea.getTopRootTask());
-
- final Task anotherAlwaysOnTopStack = taskDisplayArea.createRootTask(
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- anotherAlwaysOnTopStack.setAlwaysOnTop(true);
- taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopStack,
- false /* includingParents */);
- assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop());
- int topPosition = taskDisplayArea.getRootTaskCount() - 1;
- // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the
- // existing alwaysOnTop stack.
- assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack));
-
- final Task nonAlwaysOnTopStack = taskDisplayArea.createRootTask(
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- assertEquals(taskDisplayArea, nonAlwaysOnTopStack.getDisplayArea());
- topPosition = taskDisplayArea.getRootTaskCount() - 1;
- // Ensure the non-alwaysOnTop stack is put below the three alwaysOnTop stacks, but above the
- // existing other non-alwaysOnTop stacks.
- assertEquals(topPosition - 3, getTaskIndexOf(taskDisplayArea, nonAlwaysOnTopStack));
-
- anotherAlwaysOnTopStack.setAlwaysOnTop(false);
- taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopStack,
- false /* includingParents */);
- assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop());
- // Ensure, when always on top is turned off for a stack, the stack is put just below all
- // other always on top stacks.
- assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack));
- anotherAlwaysOnTopStack.setAlwaysOnTop(true);
-
- // Ensure always on top state changes properly when windowing mode changes.
- anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop());
- assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack));
- anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop());
- assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack));
-
- final Task dreamStack = taskDisplayArea.createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, true /* onTop */);
- assertEquals(taskDisplayArea, dreamStack.getDisplayArea());
- assertTrue(dreamStack.isAlwaysOnTop());
- topPosition = taskDisplayArea.getRootTaskCount() - 1;
- // Ensure dream shows above all activities, including PiP
- assertEquals(dreamStack, taskDisplayArea.getTopRootTask());
- assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, pinnedStack));
-
- final Task assistStack = taskDisplayArea.createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
- assertEquals(taskDisplayArea, assistStack.getDisplayArea());
- assertFalse(assistStack.isAlwaysOnTop());
- topPosition = taskDisplayArea.getRootTaskCount() - 1;
-
- // Ensure Assistant shows as a non-always-on-top activity when config_assistantOnTopOfDream
- // is false and on top of everything when true.
- final boolean isAssistantOnTop = mContext.getResources()
- .getBoolean(com.android.internal.R.bool.config_assistantOnTopOfDream);
- assertEquals(isAssistantOnTop ? topPosition : topPosition - 4,
- getTaskIndexOf(taskDisplayArea, assistStack));
- }
-
- @Test
- public void testRemoveRootTaskInWindowingModes() {
- removeStackTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes(
- WINDOWING_MODE_FULLSCREEN));
- }
-
- @Test
- public void testRemoveStackWithActivityTypes() {
- removeStackTests(() -> mRootWindowContainer.removeRootTasksWithActivityTypes(
- ACTIVITY_TYPE_STANDARD));
- }
-
- private void removeStackTests(Runnable runnable) {
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- final Task stack1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, ON_TOP);
- final Task stack2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, ON_TOP);
- final Task stack3 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, ON_TOP);
- final Task stack4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, ON_TOP);
- final Task task1 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack1).build();
- final Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack2).build();
- final Task task3 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack3).build();
- final Task task4 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack4).build();
-
- // Reordering stacks while removing stacks.
- doAnswer(invocation -> {
- taskDisplayArea.positionChildAt(POSITION_TOP, stack3, false /*includingParents*/);
- return true;
- }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
-
- // Removing stacks from the display while removing stacks.
- doAnswer(invocation -> {
- taskDisplayArea.removeRootTask(stack2);
- return true;
- }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
-
- runnable.run();
- verify(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
- verify(mSupervisor).removeTask(eq(task3), anyBoolean(), anyBoolean(), any());
- verify(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
- verify(mSupervisor).removeTask(eq(task1), anyBoolean(), anyBoolean(), any());
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index ce5fc40..678defe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -405,7 +405,7 @@
public void testGetAnimationTargets_taskContainsMultipleTasks() {
// [DisplayContent] - [Task] -+- [Task1] - [ActivityRecord1] (opening, invisible)
// +- [Task2] - [ActivityRecord2] (closing, visible)
- final Task parentTask = createTaskStackOnDisplay(mDisplayContent);
+ final Task parentTask = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
activity1.setVisible(false);
activity1.mVisibleRequested = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 83aca5e..c3279bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -177,13 +177,13 @@
}
@Test
- public void testCleanAppTransitionWhenTaskStackReparent() {
+ public void testCleanAppTransitionWhenRootTaskReparent() {
// Create 2 displays & presume both display the state is ON for ready to display & animate.
final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);
- final Task stack1 = createTaskStackOnDisplay(dc1);
- final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final Task rootTask1 = createTask(dc1);
+ final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
final ActivityRecord activity1 = createNonAttachedActivityRecord(dc1);
task1.addChild(activity1, 0);
@@ -198,8 +198,8 @@
dc1.mOpeningApps.add(activity1);
assertTrue(dc1.mOpeningApps.size() > 0);
- // Move stack to another display.
- stack1.reparent(dc2.getDefaultTaskDisplayArea(), true);
+ // Move root task to another display.
+ rootTask1.reparent(dc2.getDefaultTaskDisplayArea(), true);
// Verify if token are cleared from both pending transition list in former display.
assertFalse(dc1.mOpeningApps.contains(activity1));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 24b4f65..0afd39f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -16,10 +16,12 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
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;
@@ -65,6 +67,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
@@ -350,29 +353,29 @@
}
/**
- * This tests stack movement between displays and proper stack's, task's and app token's display
- * container references updates.
+ * This tests root task movement between displays and proper root task's, task's and app token's
+ * display container references updates.
*/
@Test
- public void testMoveStackBetweenDisplays() {
+ public void testMoveRootTaskBetweenDisplays() {
// Create a second display.
final DisplayContent dc = createNewDisplay();
- // Add stack with activity.
- final Task stack = createTaskStackOnDisplay(dc);
- assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId());
- assertEquals(dc, stack.getDisplayContent());
+ // Add root task with activity.
+ final Task rootTask = createTask(dc);
+ assertEquals(dc.getDisplayId(), rootTask.getDisplayContent().getDisplayId());
+ assertEquals(dc, rootTask.getDisplayContent());
- final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createNonAttachedActivityRecord(dc);
task.addChild(activity, 0);
assertEquals(dc, task.getDisplayContent());
assertEquals(dc, activity.getDisplayContent());
- // Move stack to first display.
- stack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */);
- assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
- assertEquals(mDisplayContent, stack.getDisplayContent());
+ // Move root task to first display.
+ rootTask.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */);
+ assertEquals(mDisplayContent.getDisplayId(), rootTask.getDisplayContent().getDisplayId());
+ assertEquals(mDisplayContent, rootTask.getDisplayContent());
assertEquals(mDisplayContent, task.getDisplayContent());
assertEquals(mDisplayContent, activity.getDisplayContent());
}
@@ -424,7 +427,7 @@
}
/**
- * Tests tapping on a stack in different display results in window gaining focus.
+ * Tests tapping on a root task in different display results in window gaining focus.
*/
@Test
public void testInputEventBringsCorrectDisplayInFocus() {
@@ -432,16 +435,16 @@
// Create a second display
final DisplayContent dc1 = createNewDisplay();
- // Add stack with activity.
- final Task stack0 = createTaskStackOnDisplay(dc0);
- final Task task0 = createTaskInStack(stack0, 0 /* userId */);
+ // Add root task with activity.
+ final Task rootTask0 = createTask(dc0);
+ final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */);
final ActivityRecord activity = createNonAttachedActivityRecord(dc0);
task0.addChild(activity, 0);
dc0.configureDisplayPolicy();
assertNotNull(dc0.mTapDetector);
- final Task stack1 = createTaskStackOnDisplay(dc1);
- final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final Task rootTask1 = createTask(dc1);
+ final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0);
task1.addChild(activity1, 0);
dc1.configureDisplayPolicy();
@@ -889,13 +892,13 @@
final DisplayContent newDisplay = createNewDisplay();
final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
- final Task stack = mDisplayContent.getTopRootTask();
- final ActivityRecord activity = stack.topRunningActivity();
+ final Task rootTask = mDisplayContent.getTopRootTask();
+ final ActivityRecord activity = rootTask.topRunningActivity();
doReturn(true).when(activity).shouldBeVisibleUnchecked();
final WindowState appWin1 = createWindow(null, TYPE_APPLICATION, newDisplay, "appWin1");
- final Task stack1 = newDisplay.getTopRootTask();
- final ActivityRecord activity1 = stack1.topRunningActivity();
+ final Task rootTask1 = newDisplay.getTopRootTask();
+ final ActivityRecord activity1 = rootTask1.topRunningActivity();
doReturn(true).when(activity1).shouldBeVisibleUnchecked();
appWin.setHasSurface(true);
appWin1.setHasSurface(true);
@@ -934,28 +937,28 @@
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
- final Task stack = new TaskBuilder(mSupervisor)
+ final Task rootTask = new TaskBuilder(mSupervisor)
.setDisplay(dc)
.setCreateActivity(true)
.build();
- doReturn(true).when(stack).isVisible();
+ doReturn(true).when(rootTask).isVisible();
- final Task freeformStack = new TaskBuilder(mSupervisor)
+ final Task freeformRootTask = new TaskBuilder(mSupervisor)
.setDisplay(dc)
.setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
- doReturn(true).when(freeformStack).isVisible();
- freeformStack.getTopChild().setBounds(100, 100, 300, 400);
+ doReturn(true).when(freeformRootTask).isVisible();
+ freeformRootTask.getTopChild().setBounds(100, 100, 300, 400);
assertTrue(dc.getDefaultTaskDisplayArea().isRootTaskVisible(WINDOWING_MODE_FREEFORM));
- freeformStack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- stack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ freeformRootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ rootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation());
- stack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- freeformStack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ rootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ freeformRootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
}
@@ -1683,66 +1686,6 @@
}
@Test
- public void testGetOrCreateRootHomeTask_defaultDisplay() {
- TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea();
-
- // Remove the current home stack if it exists so a new one can be created below.
- Task homeTask = defaultTaskDisplayArea.getRootHomeTask();
- if (homeTask != null) {
- defaultTaskDisplayArea.removeChild(homeTask);
- }
- assertNull(defaultTaskDisplayArea.getRootHomeTask());
-
- assertNotNull(defaultTaskDisplayArea.getOrCreateRootHomeTask());
- }
-
- @Test
- public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
- DisplayContent display = createNewDisplay();
- doReturn(true).when(display).supportsSystemDecorations();
-
- // Remove the current home stack if it exists so a new one can be created below.
- TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
- Task homeTask = taskDisplayArea.getRootHomeTask();
- if (homeTask != null) {
- taskDisplayArea.removeChild(homeTask);
- }
- assertNull(taskDisplayArea.getRootHomeTask());
-
- assertNotNull(taskDisplayArea.getOrCreateRootHomeTask());
- }
-
- @Test
- public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
- DisplayContent display = createNewDisplay();
- TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
- doReturn(false).when(display).supportsSystemDecorations();
-
- assertNull(taskDisplayArea.getRootHomeTask());
- assertNull(taskDisplayArea.getOrCreateRootHomeTask());
- }
-
- @Test
- public void testGetOrCreateRootHomeTask_untrustedDisplay() {
- DisplayContent display = createNewDisplay();
- TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
- doReturn(false).when(display).isTrusted();
-
- assertNull(taskDisplayArea.getRootHomeTask());
- assertNull(taskDisplayArea.getOrCreateRootHomeTask());
- }
-
- @Test
- public void testGetOrCreateRootHomeTask_dontMoveToTop() {
- DisplayContent display = createNewDisplay();
- display.mDontMoveToTop = true;
- TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
-
- assertNull(taskDisplayArea.getRootHomeTask());
- assertNull(taskDisplayArea.getOrCreateRootHomeTask());
- }
-
- @Test
public void testValidWindowingLayer() {
final SurfaceControl windowingLayer = mDisplayContent.getWindowingLayer();
assertNotNull(windowingLayer);
@@ -1761,8 +1704,8 @@
@Test
public void testFindScrollCaptureTargetWindow_behindWindow() {
DisplayContent display = createNewDisplay();
- Task stack = createTaskStackOnDisplay(display);
- Task task = createTaskInStack(stack, 0 /* userId */);
+ Task rootTask = createTask(display);
+ Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window");
WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
@@ -1774,8 +1717,8 @@
@Test
public void testFindScrollCaptureTargetWindow_cantReceiveKeys() {
DisplayContent display = createNewDisplay();
- Task stack = createTaskStackOnDisplay(display);
- Task task = createTaskInStack(stack, 0 /* userId */);
+ Task rootTask = createTask(display);
+ Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window");
WindowState invisible = createWindow(null, TYPE_APPLICATION, "invisible");
invisible.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false
@@ -1788,8 +1731,8 @@
@Test
public void testFindScrollCaptureTargetWindow_taskId() {
DisplayContent display = createNewDisplay();
- Task stack = createTaskStackOnDisplay(display);
- Task task = createTaskInStack(stack, 0 /* userId */);
+ Task rootTask = createTask(display);
+ Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window");
WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
@@ -1800,8 +1743,8 @@
@Test
public void testFindScrollCaptureTargetWindow_taskIdCantReceiveKeys() {
DisplayContent display = createNewDisplay();
- Task stack = createTaskStackOnDisplay(display);
- Task task = createTaskInStack(stack, 0 /* userId */);
+ Task rootTask = createTask(display);
+ Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window");
window.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false
WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
@@ -1828,7 +1771,7 @@
@Test
public void testSetWindowingModeAtomicallyUpdatesWindoingModeAndDisplayWindowingMode() {
final DisplayContent dc = createNewDisplay();
- final Task stack = new TaskBuilder(mSupervisor)
+ final Task rootTask = new TaskBuilder(mSupervisor)
.setDisplay(dc)
.build();
doAnswer(invocation -> {
@@ -1837,7 +1780,7 @@
assertEquals(config.windowConfiguration.getWindowingMode(),
config.windowConfiguration.getDisplayWindowingMode());
return null;
- }).when(stack).onConfigurationChanged(any());
+ }).when(rootTask).onConfigurationChanged(any());
dc.setWindowingMode(WINDOWING_MODE_FREEFORM);
dc.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
@@ -2072,6 +2015,130 @@
0 /* delta */);
}
+ /**
+ * Verifies {@link DisplayContent#remove} should not resume home root task on the removing
+ * display.
+ */
+ @Test
+ public void testNotResumeHomeRootTaskOnRemovingDisplay() {
+ // Create a display which supports system decoration and allows reparenting root tasks to
+ // another display when the display is removed.
+ final DisplayContent display = new TestDisplayContent.Builder(
+ mAtm, 1000, 1500).setSystemDecorations(true).build();
+ doReturn(false).when(display).shouldDestroyContentOnRemove();
+
+ // Put home root task on the display.
+ final Task homeRootTask = new TaskBuilder(mSupervisor)
+ .setDisplay(display).setActivityType(ACTIVITY_TYPE_HOME).build();
+
+ // Put a finishing standard activity which will be reparented.
+ final Task rootTask = createTaskWithActivity(display.getDefaultTaskDisplayArea(),
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true /* twoLevelTask */);
+ rootTask.topRunningActivity().makeFinishingLocked();
+
+ clearInvocations(homeRootTask);
+ display.remove();
+
+ // The removed display should have no focused root task and its home root task should never
+ // resume.
+ assertNull(display.getFocusedRootTask());
+ verify(homeRootTask, never()).resumeTopActivityUncheckedLocked(any(), any());
+ }
+
+ /**
+ * Verifies the correct activity is returned when querying the top running activity.
+ */
+ @Test
+ public void testTopRunningActivity() {
+ final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
+ final KeyguardController keyguard = mSupervisor.getKeyguardController();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ final ActivityRecord activity = rootTask.getTopNonFinishingActivity();
+
+ // Create empty root task on top.
+ final Task emptyRootTask = new TaskBuilder(mSupervisor).build();
+
+ // Make sure the top running activity is not affected when keyguard is not locked.
+ assertTopRunningActivity(activity, display);
+
+ // Check to make sure activity not reported when it cannot show on lock and lock is on.
+ doReturn(true).when(keyguard).isKeyguardLocked();
+ assertEquals(activity, display.topRunningActivity());
+ assertNull(display.topRunningActivity(true /* considerKeyguardState */));
+
+ // Move root task with activity to top.
+ rootTask.moveToFront("testRootTaskToFront");
+ assertEquals(rootTask, display.getFocusedRootTask());
+ assertEquals(activity, display.topRunningActivity());
+ assertNull(display.topRunningActivity(true /* considerKeyguardState */));
+
+ // Add activity that should be shown on the keyguard.
+ final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mAtm)
+ .setTask(rootTask)
+ .setActivityFlags(FLAG_SHOW_WHEN_LOCKED)
+ .build();
+
+ // Ensure the show when locked activity is returned.
+ assertTopRunningActivity(showWhenLockedActivity, display);
+
+ // Move empty root task to front. The running activity in focusable root task which below
+ // the empty root task should be returned.
+ emptyRootTask.moveToFront("emptyRootTaskToFront");
+ assertEquals(rootTask, display.getFocusedRootTask());
+ assertTopRunningActivity(showWhenLockedActivity, display);
+ }
+
+ private static void assertTopRunningActivity(ActivityRecord top, DisplayContent display) {
+ assertEquals(top, display.topRunningActivity());
+ assertEquals(top, display.topRunningActivity(true /* considerKeyguardState */));
+ }
+
+ @Test
+ public void testRemoveRootTaskInWindowingModes() {
+ removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes(
+ WINDOWING_MODE_FULLSCREEN));
+ }
+
+ @Test
+ public void testRemoveRootTaskWithActivityTypes() {
+ removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksWithActivityTypes(
+ ACTIVITY_TYPE_STANDARD));
+ }
+
+ private void removeRootTaskTests(Runnable runnable) {
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, ON_TOP);
+ final Task rootTask2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, ON_TOP);
+ final Task rootTask3 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, ON_TOP);
+ final Task rootTask4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, ON_TOP);
+ final Task task1 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask1).build();
+ final Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask2).build();
+ final Task task3 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask3).build();
+ final Task task4 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask4).build();
+
+ // Reordering root tasks while removing root tasks.
+ doAnswer(invocation -> {
+ taskDisplayArea.positionChildAt(POSITION_TOP, rootTask3, false /*includingParents*/);
+ return true;
+ }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
+
+ // Removing root tasks from the display while removing root tasks.
+ doAnswer(invocation -> {
+ taskDisplayArea.removeRootTask(rootTask2);
+ return true;
+ }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
+
+ runnable.run();
+ verify(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
+ verify(mSupervisor).removeTask(eq(task3), anyBoolean(), anyBoolean(), any());
+ verify(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
+ verify(mSupervisor).removeTask(eq(task1), anyBoolean(), anyBoolean(), any());
+ }
+
private boolean isOptionsPanelAtRight(int displayId) {
return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 4e2697a..1bddd7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -17,8 +17,6 @@
package com.android.server.wm;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
@@ -124,9 +122,8 @@
*/
private WindowState createDropTargetWindow(String name, int ownerId) {
final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
- final Task stack = createTaskStackOnDisplay(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task task = createTaskInStack(stack, ownerId);
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, ownerId);
task.addChild(activity, 0);
// Use a new TestIWindow so we don't collect events for other windows
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 f97e794..7d137bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -488,9 +488,9 @@
@Test
public void testIsAnimatingByRecents() {
final ActivityRecord homeActivity = createHomeActivity();
- final Task rootTask = createTaskStackOnDisplay(mDefaultDisplay);
- final Task childTask = createTaskInStack(rootTask, 0 /* userId */);
- final Task leafTask = createTaskInStack(childTask, 0 /* userId */);
+ final Task rootTask = createTask(mDefaultDisplay);
+ final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
+ final Task leafTask = createTaskInRootTask(childTask, 0 /* userId */);
spyOn(leafTask);
doReturn(true).when(leafTask).isVisible();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
deleted file mode 100644
index 8388f2a..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ /dev/null
@@ -1,953 +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 static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.TYPE_VIRTUAL;
-import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-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.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.contains;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.refEq;
-
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
-import android.util.Pair;
-
-import androidx.test.filters.MediumTest;
-
-import com.android.internal.app.ResolverActivity;
-import com.android.server.wm.Task.ActivityState;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Tests for the {@link RootWindowContainer} class.
- *
- * Build/Install/Run:
- * atest WmTests:RootActivityContainerTests
- */
-@MediumTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class RootActivityContainerTests extends WindowTestsBase {
-
- @Before
- public void setUp() throws Exception {
- doNothing().when(mAtm).updateSleepIfNeededLocked();
- }
-
- /**
- * This test ensures that we do not try to restore a task based off an invalid task id. We
- * should expect {@code null} to be returned in this case.
- */
- @Test
- public void testRestoringInvalidTask() {
- mRootWindowContainer.getDefaultDisplay().removeAllTasks();
- Task task = mRootWindowContainer.anyTaskForId(0 /*taskId*/,
- MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
- assertNull(task);
- }
-
- /**
- * This test ensures that an existing task in the pinned stack is moved to the fullscreen
- * activity stack when a new task is added.
- */
- @Test
- public void testReplacingTaskInPinnedStack() {
- Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
-
- fullscreenTask.moveToFront("testReplacingTaskInPinnedStack");
-
- // Ensure full screen stack has both tasks.
- ensureStackPlacement(fullscreenTask, firstActivity, secondActivity);
-
- // Move first activity to pinned stack.
- mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove");
-
- final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
- Task pinnedStack = taskDisplayArea.getRootPinnedTask();
- // Ensure a task has moved over.
- ensureStackPlacement(pinnedStack, firstActivity);
- ensureStackPlacement(fullscreenTask, secondActivity);
-
- // Move second activity to pinned stack.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove");
-
- // Need to get stacks again as a new instance might have been created.
- pinnedStack = taskDisplayArea.getRootPinnedTask();
- fullscreenTask = taskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD);
- // Ensure stacks have swapped tasks.
- ensureStackPlacement(pinnedStack, secondActivity);
- ensureStackPlacement(fullscreenTask, firstActivity);
- }
-
- @Test
- public void testMovingBottomMostStackActivityToPinnedStack() {
- final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
- final Task task = firstActivity.getTask();
-
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
- .setTask(fullscreenTask).build();
-
- fullscreenTask.moveTaskToBack(task);
-
- // Ensure full screen stack has both tasks.
- ensureStackPlacement(fullscreenTask, firstActivity, secondActivity);
- assertEquals(task.getTopMostActivity(), secondActivity);
- firstActivity.setState(STOPPED, "testMovingBottomMostStackActivityToPinnedStack");
-
-
- // Move first activity to pinned stack.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
-
- assertTrue(firstActivity.mRequestForceTransition);
- }
-
- private static void ensureStackPlacement(Task task, ActivityRecord... activities) {
- final ArrayList<ActivityRecord> taskActivities = new ArrayList<>();
-
- task.forAllActivities((Consumer<ActivityRecord>) taskActivities::add, false);
-
- assertEquals("Expecting " + Arrays.deepToString(activities) + " got " + taskActivities,
- taskActivities.size(), activities != null ? activities.length : 0);
-
- if (activities == null) {
- return;
- }
-
- for (ActivityRecord activity : activities) {
- assertTrue(taskActivities.contains(activity));
- }
- }
-
- @Test
- public void testApplySleepTokens() {
- final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
- final KeyguardController keyguard = mSupervisor.getKeyguardController();
- final Task stack = new TaskBuilder(mSupervisor)
- .setDisplay(display)
- .setOnTop(false)
- .build();
-
- // Make sure we wake and resume in the case the display is turning on and the keyguard is
- // not showing.
- verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/,
- false /* displayShouldSleep */, true /* isFocusedStack */,
- false /* keyguardShowing */, true /* expectWakeFromSleep */,
- true /* expectResumeTopActivity */);
-
- // Make sure we wake and don't resume when the display is turning on and the keyguard is
- // showing.
- verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/,
- false /* displayShouldSleep */, true /* isFocusedStack */,
- true /* keyguardShowing */, true /* expectWakeFromSleep */,
- false /* expectResumeTopActivity */);
-
- // Make sure we wake and don't resume when the display is turning on and the keyguard is
- // not showing as unfocused.
- verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/,
- false /* displayShouldSleep */, false /* isFocusedStack */,
- false /* keyguardShowing */, true /* expectWakeFromSleep */,
- false /* expectResumeTopActivity */);
-
- // Should not do anything if the display state hasn't changed.
- verifySleepTokenBehavior(display, keyguard, stack, false /*displaySleeping*/,
- false /* displayShouldSleep */, true /* isFocusedStack */,
- false /* keyguardShowing */, false /* expectWakeFromSleep */,
- false /* expectResumeTopActivity */);
- }
-
- private void verifySleepTokenBehavior(DisplayContent display, KeyguardController keyguard,
- Task stack, boolean displaySleeping, boolean displayShouldSleep,
- boolean isFocusedStack, boolean keyguardShowing, boolean expectWakeFromSleep,
- boolean expectResumeTopActivity) {
- reset(stack);
-
- doReturn(displayShouldSleep).when(display).shouldSleep();
- doReturn(displaySleeping).when(display).isSleeping();
- doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt());
-
- doReturn(isFocusedStack).when(stack).isFocusedRootTaskOnDisplay();
- doReturn(isFocusedStack ? stack : null).when(display).getFocusedRootTask();
- TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea();
- doReturn(isFocusedStack ? stack : null).when(defaultTaskDisplayArea).getFocusedRootTask();
- mRootWindowContainer.applySleepTokens(true);
- verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked();
- verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked(
- null /* target */, null /* targetOptions */);
- }
-
- @Test
- public void testAwakeFromSleepingWithAppConfiguration() {
- final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
- final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- activity.moveFocusableActivityToTop("test");
- assertTrue(activity.getRootTask().isFocusedRootTaskOnDisplay());
- ActivityRecordTests.setRotatedScreenOrientationSilently(activity);
-
- final Configuration rotatedConfig = new Configuration();
- display.computeScreenConfiguration(rotatedConfig, display.getDisplayRotation()
- .rotationForOrientation(activity.getOrientation(), display.getRotation()));
- assertNotEquals(activity.getConfiguration().orientation, rotatedConfig.orientation);
- // Assume the activity was shown in different orientation. For example, the top activity is
- // landscape and the portrait lockscreen is shown.
- activity.setLastReportedConfiguration(
- new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
- activity.setState(ActivityState.STOPPED, "sleep");
-
- display.setIsSleeping(true);
- doReturn(false).when(display).shouldSleep();
- // Allow to resume when awaking.
- setBooted(mAtm);
- mRootWindowContainer.applySleepTokens(true);
-
- // The display orientation should be changed by the activity so there is no relaunch.
- verify(activity, never()).relaunchActivityLocked(anyBoolean());
- assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation);
- }
-
- /**
- * Verifies that removal of activity with task and stack is done correctly.
- */
- @Test
- public void testRemovingStackOnAppCrash() {
- final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
- .getDefaultTaskDisplayArea();
- final int originalStackCount = defaultTaskDisplayArea.getRootTaskCount();
- final Task stack = defaultTaskDisplayArea.createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(stack).build();
-
- assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getRootTaskCount());
-
- // Let's pretend that the app has crashed.
- firstActivity.app.setThread(null);
- mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test");
-
- // Verify that the stack was removed.
- assertEquals(originalStackCount, defaultTaskDisplayArea.getRootTaskCount());
- }
-
- /**
- * Verifies that removal of activities with task and stack is done correctly when there are
- * several task display areas.
- */
- @Test
- public void testRemovingStackOnAppCrash_multipleDisplayAreas() {
- final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
- .getDefaultTaskDisplayArea();
- final int originalStackCount = defaultTaskDisplayArea.getRootTaskCount();
- final Task stack = defaultTaskDisplayArea.createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(stack).build();
- assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getRootTaskCount());
-
- final DisplayContent dc = defaultTaskDisplayArea.getDisplayContent();
- final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
- dc, mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST);
- final Task secondStack = secondTaskDisplayArea.createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- new ActivityBuilder(mAtm).setTask(secondStack).setUseProcess(firstActivity.app).build();
- assertEquals(1, secondTaskDisplayArea.getRootTaskCount());
-
- // Let's pretend that the app has crashed.
- firstActivity.app.setThread(null);
- mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test");
-
- // Verify that the stacks were removed.
- assertEquals(originalStackCount, defaultTaskDisplayArea.getRootTaskCount());
- assertEquals(0, secondTaskDisplayArea.getRootTaskCount());
- }
-
- @Test
- public void testFocusability() {
- final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
- .getDefaultTaskDisplayArea();
- final Task stack = defaultTaskDisplayArea.createRootTask(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build();
-
- // Created stacks are focusable by default.
- assertTrue(stack.isTopActivityFocusable());
- assertTrue(activity.isFocusable());
-
- // If the stack is made unfocusable, its activities should inherit that.
- stack.setFocusable(false);
- assertFalse(stack.isTopActivityFocusable());
- assertFalse(activity.isFocusable());
-
- final Task pinnedStack = defaultTaskDisplayArea.createRootTask(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
- .setTask(pinnedStack).build();
-
- // We should not be focusable when in pinned mode
- assertFalse(pinnedStack.isTopActivityFocusable());
- assertFalse(pinnedActivity.isFocusable());
-
- // Add flag forcing focusability.
- pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
-
- // Task with FLAG_ALWAYS_FOCUSABLE should be focusable.
- assertTrue(pinnedStack.isTopActivityFocusable());
- assertTrue(pinnedActivity.isFocusable());
- }
-
- /**
- * Verify that split-screen primary stack will be chosen if activity is launched that targets
- * split-screen secondary, but a matching existing instance is found on top of split-screen
- * primary stack.
- */
- @Test
- public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() {
- // Create primary split-screen stack with a task and an activity.
- final Task primaryStack = mRootWindowContainer.getDefaultTaskDisplayArea()
- .createRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task task = new TaskBuilder(mSupervisor).setParentTask(primaryStack).build();
- final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build();
-
- // Find a launch stack for the top activity in split-screen primary, while requesting
- // split-screen secondary.
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- final Task result =
- mRootWindowContainer.getLaunchRootTask(r, options, task, true /* onTop */);
-
- // Assert that the primary stack is returned.
- assertEquals(primaryStack, result);
- }
-
- /**
- * Verify that home stack would be moved to front when the top activity is Recents.
- */
- @Test
- public void testFindTaskToMoveToFrontWhenRecentsOnTop() {
- // Create stack/task on default display.
- final Task targetStack = new TaskBuilder(mSupervisor)
- .setCreateActivity(true)
- .setOnTop(false)
- .build();
- final Task targetTask = targetStack.getBottomMostTask();
-
- // Create Recents on top of the display.
- final Task stack = new TaskBuilder(mSupervisor)
- .setCreateActivity(true)
- .setActivityType(ACTIVITY_TYPE_RECENTS)
- .build();
-
- final String reason = "findTaskToMoveToFront";
- mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
- false);
-
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- verify(taskDisplayArea).moveHomeRootTaskToFront(contains(reason));
- }
-
- /**
- * Verify that home stack won't be moved to front if the top activity on other display is
- * Recents.
- */
- @Test
- public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() {
- // Create tasks on default display.
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- final Task targetRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final Task targetTask = new TaskBuilder(mSupervisor).setParentTask(targetRootTask).build();
-
- // Create Recents on secondary display.
- final TestDisplayContent secondDisplay = addNewDisplayContentAt(
- DisplayContent.POSITION_TOP);
- final Task rootTask = secondDisplay.getDefaultTaskDisplayArea()
- .createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */);
- new ActivityBuilder(mAtm).setTask(rootTask).build();
-
- final String reason = "findTaskToMoveToFront";
- mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
- false);
-
- verify(taskDisplayArea, never()).moveHomeRootTaskToFront(contains(reason));
- }
-
- /**
- * Verify if a stack is not at the topmost position, it should be able to resume its activity if
- * the stack is the top focused.
- */
- @Test
- public void testResumeActivityWhenNonTopmostStackIsTopFocused() {
- // Create a root task at bottom.
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, false /* onTop */));
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
- taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/);
-
- // Assume the task is not at the topmost position (e.g. behind always-on-top stacks) but it
- // is the current top focused task.
- assertFalse(rootTask.isTopRootTaskInDisplayArea());
- doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
-
- // Use the task as target to resume.
- mRootWindowContainer.resumeFocusedTasksTopActivities(
- rootTask, activity, null /* targetOptions */);
-
- // Verify the target task should resume its activity.
- verify(rootTask, times(1)).resumeTopActivityUncheckedLocked(
- eq(activity), eq(null /* targetOptions */));
- }
-
- /**
- * Verify that home activity will be started on a display even if another display has a
- * focusable activity.
- */
- @Test
- public void testResumeFocusedStacksStartsHomeActivity_NoActivities() {
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- taskDisplayArea.getRootHomeTask().removeIfPossible();
- taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
-
- doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
-
- mAtm.setBooted(true);
-
- // Trigger resume on all displays
- mRootWindowContainer.resumeFocusedTasksTopActivities();
-
- // Verify that home activity was started on the default display
- verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea));
- }
-
- /**
- * Verify that home activity will be started on a display even if another display has a
- * focusable activity.
- */
- @Test
- public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() {
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- taskDisplayArea.getRootHomeTask().removeIfPossible();
- taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
-
- // Create an activity on secondary display.
- final TestDisplayContent secondDisplay = addNewDisplayContentAt(
- DisplayContent.POSITION_TOP);
- final Task rootTask = secondDisplay.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- new ActivityBuilder(mAtm).setTask(rootTask).build();
-
- doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
-
- mAtm.setBooted(true);
-
- // Trigger resume on all displays
- mRootWindowContainer.resumeFocusedTasksTopActivities();
-
- // Verify that home activity was started on the default display
- verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea));
- }
-
- /**
- * Verify that a lingering transition is being executed in case the activity to be resumed is
- * already resumed
- */
- @Test
- public void testResumeActivityLingeringTransition() {
- // Create a root task at top.
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, false /* onTop */));
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setTask(rootTask).setOnTop(true).build();
- activity.setState(ActivityState.RESUMED, "test");
-
- // Assume the task is at the topmost position
- assertTrue(rootTask.isTopRootTaskInDisplayArea());
-
- // Use the task as target to resume.
- mRootWindowContainer.resumeFocusedTasksTopActivities();
-
- // Verify the lingering app transition is being executed because it's already resumed
- verify(rootTask, times(1)).executeAppTransition(any());
- }
-
- @Test
- public void testResumeActivityLingeringTransition_notExecuted() {
- // Create a root task at bottom.
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, false /* onTop */));
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setTask(rootTask).setOnTop(true).build();
- activity.setState(ActivityState.RESUMED, "test");
- taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/);
-
- // Assume the task is at the topmost position
- assertFalse(rootTask.isTopRootTaskInDisplayArea());
- doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
-
- // Use the task as target to resume.
- mRootWindowContainer.resumeFocusedTasksTopActivities();
-
- // Verify the lingering app transition is being executed because it's already resumed
- verify(rootTask, never()).executeAppTransition(any());
- }
-
- /**
- * Tests that home activities can be started on the displays that supports system decorations.
- */
- @Test
- public void testStartHomeOnAllDisplays() {
- mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
- mockResolveSecondaryHomeActivity();
-
- // Create secondary displays.
- final TestDisplayContent secondDisplay =
- new TestDisplayContent.Builder(mAtm, 1000, 1500)
- .setSystemDecorations(true).build();
-
- doReturn(true).when(mRootWindowContainer)
- .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean());
- doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
- anyBoolean());
-
- mRootWindowContainer.startHomeOnAllDisplays(0, "testStartHome");
-
- assertTrue(mRootWindowContainer.getDefaultDisplay().getTopRootTask().isActivityTypeHome());
- assertNotNull(secondDisplay.getTopRootTask());
- assertTrue(secondDisplay.getTopRootTask().isActivityTypeHome());
- }
-
- /**
- * Tests that home activities won't be started before booting when display added.
- */
- @Test
- public void testNotStartHomeBeforeBoot() {
- final int displayId = 1;
- final boolean isBooting = mAtm.mAmInternal.isBooting();
- final boolean isBooted = mAtm.mAmInternal.isBooted();
- try {
- mAtm.mAmInternal.setBooting(false);
- mAtm.mAmInternal.setBooted(false);
- mRootWindowContainer.onDisplayAdded(displayId);
- verify(mRootWindowContainer, never()).startHomeOnDisplay(anyInt(), any(), anyInt());
- } finally {
- mAtm.mAmInternal.setBooting(isBooting);
- mAtm.mAmInternal.setBooted(isBooted);
- }
- }
-
- /**
- * Tests whether home can be started if being instrumented.
- */
- @Test
- public void testCanStartHomeWhenInstrumented() {
- final ActivityInfo info = new ActivityInfo();
- info.applicationInfo = new ApplicationInfo();
- final WindowProcessController app = mock(WindowProcessController.class);
- doReturn(app).when(mAtm).getProcessController(any(), anyInt());
-
- // Can not start home if we don't want to start home while home is being instrumented.
- doReturn(true).when(app).isInstrumenting();
- final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
- .getDefaultTaskDisplayArea();
- assertFalse(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
- false /* allowInstrumenting*/));
-
- // Can start home for other cases.
- assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
- true /* allowInstrumenting*/));
-
- doReturn(false).when(app).isInstrumenting();
- assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
- false /* allowInstrumenting*/));
- assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
- true /* allowInstrumenting*/));
- }
-
- /**
- * Tests that secondary home activity should not be resolved if device is still locked.
- */
- @Test
- public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() {
- // Create secondary displays.
- final TestDisplayContent secondDisplay =
- new TestDisplayContent.Builder(mAtm, 1000, 1500)
- .setSystemDecorations(true).build();
-
- // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
- final int currentUser = mRootWindowContainer.mCurrentUser;
- mRootWindowContainer.mCurrentUser = -1;
-
- mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome",
- secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */);
-
- try {
- verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any());
- } finally {
- mRootWindowContainer.mCurrentUser = currentUser;
- }
- }
-
- /**
- * Tests that secondary home activity should not be resolved if display does not support system
- * decorations.
- */
- @Test
- public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() {
- // Create secondary displays.
- final TestDisplayContent secondDisplay =
- new TestDisplayContent.Builder(mAtm, 1000, 1500)
- .setSystemDecorations(false).build();
-
- mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome",
- secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */);
-
- verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any());
- }
-
- /**
- * Tests that when starting {@link #ResolverActivity} for home, it should use the standard
- * activity type (in a new stack) so the order of back stack won't be broken.
- */
- @Test
- public void testStartResolverActivityForHome() {
- final ActivityInfo info = new ActivityInfo();
- info.applicationInfo = new ApplicationInfo();
- info.applicationInfo.packageName = "android";
- info.name = ResolverActivity.class.getName();
- doReturn(info).when(mRootWindowContainer).resolveHomeActivity(anyInt(), any());
-
- mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "test", DEFAULT_DISPLAY);
- final ActivityRecord resolverActivity = mRootWindowContainer.topRunningActivity();
-
- assertEquals(info, resolverActivity.info);
- assertEquals(ACTIVITY_TYPE_STANDARD, resolverActivity.getRootTask().getActivityType());
- }
-
- /**
- * Tests that secondary home should be selected if primary home not set.
- */
- @Test
- public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSet() {
- // Setup: primary home not set.
- final Intent primaryHomeIntent = mAtm.getHomeIntent();
- final ActivityInfo aInfoPrimary = new ActivityInfo();
- aInfoPrimary.name = ResolverActivity.class.getName();
- doReturn(aInfoPrimary).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
- refEq(primaryHomeIntent));
- // Setup: set secondary home.
- mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */);
-
- // Run the test.
- final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
- .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
- final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
- assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
- assertEquals(aInfoSecondary.applicationInfo.packageName,
- resolvedInfo.first.applicationInfo.packageName);
- }
-
- /**
- * Tests that the default secondary home activity is always picked when it is in forced by
- * config_useSystemProvidedLauncherForSecondary.
- */
- @Test
- public void testResolveSecondaryHomeActivityForced() {
- // SetUp: set primary home.
- mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
- // SetUp: set secondary home and force it.
- mockResolveHomeActivity(false /* primaryHome */, true /* forceSystemProvided */);
- final Intent secondaryHomeIntent =
- mAtm.getSecondaryHomeIntent(null /* preferredPackage */);
- final List<ResolveInfo> resolutions = new ArrayList<>();
- final ResolveInfo resolveInfo = new ResolveInfo();
- final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
- resolveInfo.activityInfo = aInfoSecondary;
- resolutions.add(resolveInfo);
- doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(),
- refEq(secondaryHomeIntent));
- doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
- anyBoolean());
-
- // Run the test.
- final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
- .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
- assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
- assertEquals(aInfoSecondary.applicationInfo.packageName,
- resolvedInfo.first.applicationInfo.packageName);
- }
-
- /**
- * Tests that secondary home should be selected if primary home not support secondary displays
- * or there is no matched activity in the same package as selected primary home.
- */
- @Test
- public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSupportMultiDisplay() {
- // Setup: there is no matched activity in the same package as selected primary home.
- mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
- final List<ResolveInfo> resolutions = new ArrayList<>();
- doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
- // Setup: set secondary home.
- mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */);
-
- // Run the test.
- final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
- .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
- final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
- assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
- assertEquals(aInfoSecondary.applicationInfo.packageName,
- resolvedInfo.first.applicationInfo.packageName);
- }
- /**
- * Tests that primary home activity should be selected if it already support secondary displays.
- */
- @Test
- public void testResolveSecondaryHomeActivityWhenPrimaryHomeSupportMultiDisplay() {
- // SetUp: set primary home.
- mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
- // SetUp: put primary home info on 2nd item
- final List<ResolveInfo> resolutions = new ArrayList<>();
- final ResolveInfo infoFake1 = new ResolveInfo();
- infoFake1.activityInfo = new ActivityInfo();
- infoFake1.activityInfo.name = "fakeActivity1";
- infoFake1.activityInfo.applicationInfo = new ApplicationInfo();
- infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1";
- final ResolveInfo infoFake2 = new ResolveInfo();
- final ActivityInfo aInfoPrimary = getFakeHomeActivityInfo(true /* primaryHome */);
- infoFake2.activityInfo = aInfoPrimary;
- resolutions.add(infoFake1);
- resolutions.add(infoFake2);
- doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
-
- doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
- anyBoolean());
-
- // Run the test.
- final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
- .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
- assertEquals(aInfoPrimary.name, resolvedInfo.first.name);
- assertEquals(aInfoPrimary.applicationInfo.packageName,
- resolvedInfo.first.applicationInfo.packageName);
- }
-
- /**
- * Tests that the first one that matches should be selected if there are multiple activities.
- */
- @Test
- public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() {
- // SetUp: set primary home.
- mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
- // Setup: prepare two eligible activity info.
- final List<ResolveInfo> resolutions = new ArrayList<>();
- final ResolveInfo infoFake1 = new ResolveInfo();
- infoFake1.activityInfo = new ActivityInfo();
- infoFake1.activityInfo.name = "fakeActivity1";
- infoFake1.activityInfo.applicationInfo = new ApplicationInfo();
- infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1";
- final ResolveInfo infoFake2 = new ResolveInfo();
- infoFake2.activityInfo = new ActivityInfo();
- infoFake2.activityInfo.name = "fakeActivity2";
- infoFake2.activityInfo.applicationInfo = new ApplicationInfo();
- infoFake2.activityInfo.applicationInfo.packageName = "fakePackage2";
- resolutions.add(infoFake1);
- resolutions.add(infoFake2);
- doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
-
- doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
- anyBoolean());
-
- // Use the first one of matched activities in the same package as selected primary home.
- final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
- .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
-
- assertEquals(infoFake1.activityInfo.applicationInfo.packageName,
- resolvedInfo.first.applicationInfo.packageName);
- assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name);
- }
-
- /**
- * Test that {@link RootWindowContainer#getLaunchRootTask} with the real caller id will get the
- * expected stack when requesting the activity launch on the secondary display.
- */
- @Test
- public void testGetLaunchStackWithRealCallerId() {
- // Create a non-system owned virtual display.
- final TestDisplayContent secondaryDisplay =
- new TestDisplayContent.Builder(mAtm, 1000, 1500)
- .setType(TYPE_VIRTUAL).setOwnerUid(100).build();
-
- // Create an activity with specify the original launch pid / uid.
- final ActivityRecord r = new ActivityBuilder(mAtm).setLaunchedFromPid(200)
- .setLaunchedFromUid(200).build();
-
- // Simulate ActivityStarter to find a launch stack for requesting the activity to launch
- // on the secondary display with realCallerId.
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(secondaryDisplay.mDisplayId);
- options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
- doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId,
- 300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info);
- final Task result = mRootWindowContainer.getLaunchRootTask(r, options,
- null /* task */, true /* onTop */, null, 300 /* test realCallerPid */,
- 300 /* test realCallerUid */);
-
- // Assert that the stack is returned as expected.
- assertNotNull(result);
- assertEquals("The display ID of the stack should same as secondary display ",
- secondaryDisplay.mDisplayId, result.getDisplayId());
- }
-
- @Test
- public void testGetValidLaunchStackOnDisplayWithCandidateRootTask() {
- // Create a root task with an activity on secondary display.
- final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mAtm, 300,
- 600).build();
- final Task task = new TaskBuilder(mSupervisor)
- .setDisplay(secondaryDisplay).build();
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
-
- // Make sure the root task is valid and can be reused on default display.
- final Task stack = mRootWindowContainer.getValidLaunchRootTaskInTaskDisplayArea(
- mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task,
- null /* options */, null /* launchParams */);
- assertEquals(task, stack);
- }
-
- @Test
- public void testSwitchUser_missingHomeRootTask() {
- final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- doReturn(fullscreenTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
-
- final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- Task homeStack = taskDisplayArea.getRootHomeTask();
- if (homeStack != null) {
- homeStack.removeImmediately();
- }
- assertNull(taskDisplayArea.getRootHomeTask());
-
- int currentUser = mRootWindowContainer.mCurrentUser;
- int otherUser = currentUser + 1;
-
- mRootWindowContainer.switchUser(otherUser, null);
-
- assertNotNull(taskDisplayArea.getRootHomeTask());
- assertEquals(taskDisplayArea.getTopRootTask(), taskDisplayArea.getRootHomeTask());
- }
-
- /**
- * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
- * info for test cases.
- *
- * @param primaryHome Indicate to use primary home intent as parameter, otherwise, use
- * secondary home intent.
- * @param forceSystemProvided Indicate to force using system provided home activity.
- */
- private void mockResolveHomeActivity(boolean primaryHome, boolean forceSystemProvided) {
- ActivityInfo targetActivityInfo = getFakeHomeActivityInfo(primaryHome);
- Intent targetIntent;
- if (primaryHome) {
- targetIntent = mAtm.getHomeIntent();
- } else {
- Resources resources = mContext.getResources();
- spyOn(resources);
- doReturn(targetActivityInfo.applicationInfo.packageName).when(resources).getString(
- com.android.internal.R.string.config_secondaryHomePackage);
- doReturn(forceSystemProvided).when(resources).getBoolean(
- com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
- targetIntent = mAtm.getSecondaryHomeIntent(null /* preferredPackage */);
- }
- doReturn(targetActivityInfo).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
- refEq(targetIntent));
- }
-
- /**
- * Mock {@link RootWindowContainer#resolveSecondaryHomeActivity} for returning consistent
- * activity info for test cases.
- */
- private void mockResolveSecondaryHomeActivity() {
- final Intent secondaryHomeIntent = mAtm
- .getSecondaryHomeIntent(null /* preferredPackage */);
- final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false);
- doReturn(Pair.create(aInfoSecondary, secondaryHomeIntent)).when(mRootWindowContainer)
- .resolveSecondaryHomeActivity(anyInt(), any());
- }
-
- private ActivityInfo getFakeHomeActivityInfo(boolean primaryHome) {
- final ActivityInfo aInfo = new ActivityInfo();
- aInfo.name = primaryHome ? "fakeHomeActivity" : "fakeSecondaryHomeActivity";
- aInfo.applicationInfo = new ApplicationInfo();
- aInfo.applicationInfo.packageName =
- primaryHome ? "fakeHomePackage" : "fakeSecondaryHomePackage";
- return aInfo;
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
similarity index 60%
rename from services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
rename to services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 3a7954b..748622b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.wm;
@@ -27,12 +27,14 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
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.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
@@ -49,6 +51,8 @@
import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove;
+import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
+import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -56,26 +60,30 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import android.app.ActivityManager;
import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
-import com.android.server.wm.TaskDisplayArea.OnRootTaskOrderChangedListener;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,15 +92,16 @@
import java.util.function.Consumer;
/**
- * Tests for the {@link ActivityStack} class.
+ * Tests for the root {@link Task} behavior.
*
* Build/Install/Run:
- * atest WmTests:ActivityStackTests
+ * atest WmTests:RootTaskTests
*/
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
-public class ActivityStackTests extends WindowTestsBase {
+public class RootTaskTests extends WindowTestsBase {
+
private TaskDisplayArea mDefaultTaskDisplayArea;
@Before
@@ -101,6 +110,172 @@
}
@Test
+ public void testRootTaskPositionChildAt() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task1 = createTaskInRootTask(rootTask, 0 /* userId */);
+ final Task task2 = createTaskInRootTask(rootTask, 1 /* userId */);
+
+ // Current user root task should be moved to top.
+ rootTask.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */);
+ assertEquals(rootTask.mChildren.get(0), task2);
+ assertEquals(rootTask.mChildren.get(1), task1);
+
+ // Non-current user won't be moved to top.
+ rootTask.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */);
+ assertEquals(rootTask.mChildren.get(0), task2);
+ assertEquals(rootTask.mChildren.get(1), task1);
+
+ // Non-leaf task should be moved to top regardless of the user id.
+ createTaskInRootTask(task2, 0 /* userId */);
+ createTaskInRootTask(task2, 1 /* userId */);
+ rootTask.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */);
+ assertEquals(rootTask.mChildren.get(0), task1);
+ assertEquals(rootTask.mChildren.get(1), task2);
+ }
+
+ @Test
+ public void testClosingAppDifferentTaskOrientation() {
+ final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
+ activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
+ activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ final WindowContainer parent = activity1.getTask().getParent();
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation());
+ mDisplayContent.mClosingApps.add(activity2);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parent.getOrientation());
+ }
+
+ @Test
+ public void testMoveTaskToBackDifferentTaskOrientation() {
+ final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
+ activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
+ activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ final WindowContainer parent = activity1.getTask().getParent();
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation());
+ }
+
+ @Test
+ public void testRootTaskRemoveImmediately() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ assertEquals(rootTask, task.getRootTask());
+
+ // Remove root task and check if its child is also removed.
+ rootTask.removeImmediately();
+ assertNull(rootTask.getDisplayContent());
+ assertNull(task.getParent());
+ }
+
+ @Test
+ public void testRemoveContainer() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+
+ assertNotNull(rootTask);
+ assertNotNull(task);
+ rootTask.removeIfPossible();
+ // Assert that the container was removed.
+ assertNull(rootTask.getParent());
+ assertEquals(0, rootTask.getChildCount());
+ assertNull(rootTask.getDisplayContent());
+ assertNull(task.getDisplayContent());
+ assertNull(task.getParent());
+ }
+
+ @Test
+ public void testRemoveContainer_deferRemoval() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+
+ // Root task removal is deferred if one of its child is animating.
+ doReturn(true).when(rootTask).hasWindowsAlive();
+ doReturn(rootTask).when(task).getAnimatingContainer(
+ eq(TRANSITION | CHILDREN), anyInt());
+
+ rootTask.removeIfPossible();
+ // For the case of deferred removal the task controller will still be connected to its
+ // container until the root task window container is removed.
+ assertNotNull(rootTask.getParent());
+ assertNotEquals(0, rootTask.getChildCount());
+ assertNotNull(task);
+
+ rootTask.removeImmediately();
+ // After removing, the task will be isolated.
+ assertNull(task.getParent());
+ assertEquals(0, task.getChildCount());
+ }
+
+ @Test
+ public void testReparent() {
+ // Create first root task on primary display.
+ final Task rootTask1 = createTask(mDisplayContent);
+ final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
+
+ // Create second display and put second root task on it.
+ final DisplayContent dc = createNewDisplay();
+ final Task rootTask2 = createTask(dc);
+
+ // Reparent
+ clearInvocations(task1); // reset the number of onDisplayChanged for task.
+ rootTask1.reparent(dc.getDefaultTaskDisplayArea(), true /* onTop */);
+ assertEquals(dc, rootTask1.getDisplayContent());
+ final int stack1PositionInParent = rootTask1.getParent().mChildren.indexOf(rootTask1);
+ final int stack2PositionInParent = rootTask1.getParent().mChildren.indexOf(rootTask2);
+ assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
+ verify(task1, times(1)).onDisplayChanged(any());
+ }
+
+ @Test
+ public void testTaskOutset() {
+ final Task task = createTask(mDisplayContent);
+ final int taskOutset = 10;
+ spyOn(task);
+ doReturn(taskOutset).when(task).getTaskOutset();
+ doReturn(true).when(task).inMultiWindowMode();
+
+ // Mock the resolved override windowing mode to non-fullscreen
+ final WindowConfiguration windowConfiguration =
+ task.getResolvedOverrideConfiguration().windowConfiguration;
+ spyOn(windowConfiguration);
+ doReturn(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
+ .when(windowConfiguration).getWindowingMode();
+
+ // Prevent adjust task dimensions
+ doNothing().when(task).adjustForMinimalTaskDimensions(any(), any(), any());
+
+ final Rect taskBounds = new Rect(200, 200, 800, 1000);
+ // Update surface position and size by the given bounds.
+ task.setBounds(taskBounds);
+
+ assertEquals(taskBounds.width() + 2 * taskOutset, task.getLastSurfaceSize().x);
+ assertEquals(taskBounds.height() + 2 * taskOutset, task.getLastSurfaceSize().y);
+ assertEquals(taskBounds.left - taskOutset, task.getLastSurfacePosition().x);
+ assertEquals(taskBounds.top - taskOutset, task.getLastSurfacePosition().y);
+ }
+
+ @Test
+ public void testActivityAndTaskGetsProperType() {
+ final Task task1 = new TaskBuilder(mSupervisor).build();
+ ActivityRecord activity1 = createNonAttachedActivityRecord(mDisplayContent);
+
+ // First activity should become standard
+ task1.addChild(activity1, 0);
+ assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity1.getActivityType());
+ assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
+
+ // Second activity should also become standard
+ ActivityRecord activity2 = createNonAttachedActivityRecord(mDisplayContent);
+ task1.addChild(activity2, WindowContainer.POSITION_TOP);
+ assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity2.getActivityType());
+ assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
+ }
+
+ @Test
public void testResumedActivity() {
final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
final Task task = r.getTask();
@@ -113,40 +288,40 @@
@Test
public void testResumedActivityFromTaskReparenting() {
- final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
final ActivityRecord r = new ActivityBuilder(mAtm)
- .setCreateTask(true).setParentTask(parentTask).build();
+ .setCreateTask(true).setParentTask(rootTask).build();
final Task task = r.getTask();
- // Ensure moving task between two stacks updates resumed activity
+ // Ensure moving task between two root tasks updates resumed activity
r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
- assertEquals(r, parentTask.getResumedActivity());
+ assertEquals(r, rootTask.getResumedActivity());
- final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build();
- task.reparent(destStack, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT,
+ final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ task.reparent(destRootTask, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT,
false /* animate */, true /* deferResume*/,
"testResumedActivityFromTaskReparenting");
- assertNull(parentTask.getResumedActivity());
- assertEquals(r, destStack.getResumedActivity());
+ assertNull(rootTask.getResumedActivity());
+ assertEquals(r, destRootTask.getResumedActivity());
}
@Test
public void testResumedActivityFromActivityReparenting() {
- final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
final ActivityRecord r = new ActivityBuilder(mAtm)
- .setCreateTask(true).setParentTask(parentTask).build();
+ .setCreateTask(true).setParentTask(rootTask).build();
final Task task = r.getTask();
- // Ensure moving task between two stacks updates resumed activity
+ // Ensure moving task between two root tasks updates resumed activity
r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
- assertEquals(r, parentTask.getResumedActivity());
+ assertEquals(r, rootTask.getResumedActivity());
- final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build();
- task.reparent(destStack, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+ final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ task.reparent(destRootTask, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
false /* animate */, false /* deferResume*/,
"testResumedActivityFromActivityReparenting");
- assertNull(parentTask.getResumedActivity());
- assertEquals(r, destStack.getResumedActivity());
+ assertNull(rootTask.getResumedActivity());
+ assertEquals(r, destRootTask.getResumedActivity());
}
@Test
@@ -155,7 +330,7 @@
// We're testing an edge case here where we have primary + fullscreen rather than secondary.
organizer.setMoveToSecondaryOnEnter(false);
- // Create primary splitscreen stack.
+ // Create primary splitscreen root task.
final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor)
.setParentTask(organizer.mPrimary)
.setOnTop(true)
@@ -168,7 +343,7 @@
primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack",
null /* task */);
- // Assert that stack is at the bottom.
+ // Assert that root task is at the bottom.
assertEquals(0, getTaskIndexOf(mDefaultTaskDisplayArea, primarySplitScreen));
// Ensure no longer in splitscreen.
@@ -182,7 +357,7 @@
@Test
public void testMoveToPrimarySplitScreenThenMoveToBack() {
TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
- // This time, start with a fullscreen activitystack
+ // This time, start with a fullscreen activity root task.
final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -196,7 +371,7 @@
primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack",
null /* task */);
- // Assert that stack is at the bottom.
+ // Assert that root task is at the bottom.
assertEquals(primarySplitScreen, organizer.mSecondary.getChildAt(0));
// Ensure that the override mode is restored to what it was (fullscreen)
@@ -208,7 +383,7 @@
public void testSplitScreenMoveToBack() {
TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
// Explicitly reparent task to primary split root to enter split mode, in which implies
- // primary on top and secondary containing the home task below another stack.
+ // primary on top and secondary containing the home task below another root task.
final Task primaryTask = mDefaultTaskDisplayArea.createRootTask(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask(
@@ -244,24 +419,24 @@
}
@Test
- public void testRemoveOrganizedTask_UpdateStackReference() {
+ public void testRemoveOrganizedTask_UpdateRootTaskReference() {
final Task rootHomeTask = mDefaultTaskDisplayArea.getRootHomeTask();
final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
.setTask(rootHomeTask)
.build();
- final Task secondaryStack = mAtm.mTaskOrganizerController.createRootTask(
+ final Task secondaryRootTask = mAtm.mTaskOrganizerController.createRootTask(
rootHomeTask.getDisplayContent(), WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
- rootHomeTask.reparent(secondaryStack, POSITION_TOP);
- assertEquals(secondaryStack, rootHomeTask.getParent());
+ rootHomeTask.reparent(secondaryRootTask, POSITION_TOP);
+ assertEquals(secondaryRootTask, rootHomeTask.getParent());
- // This should call to {@link TaskDisplayArea#removeStackReferenceIfNeeded}.
+ // This should call to {@link TaskDisplayArea#removeRootTaskReferenceIfNeeded}.
homeActivity.removeImmediately();
assertNull(mDefaultTaskDisplayArea.getRootHomeTask());
}
@Test
- public void testStackInheritsDisplayWindowingMode() {
+ public void testRootTaskInheritsDisplayWindowingMode() {
final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -276,7 +451,7 @@
}
@Test
- public void testStackOverridesDisplayWindowingMode() {
+ public void testRootTaskOverridesDisplayWindowingMode() {
final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -358,95 +533,92 @@
}
@Test
- public void testMoveStackToBackIncludingParent() {
+ public void testMoveRootTaskToBackIncludingParent() {
final TaskDisplayArea taskDisplayArea = addNewDisplayContentAt(DisplayContent.POSITION_TOP)
.getDefaultTaskDisplayArea();
- final Task stack1 = createStackForShouldBeVisibleTest(taskDisplayArea,
+ final Task rootTask1 = createTaskForShouldBeVisibleTest(taskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */,
true /* twoLevelTask */);
- final Task stack2 = createStackForShouldBeVisibleTest(taskDisplayArea,
+ final Task rootTask2 = createTaskForShouldBeVisibleTest(taskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */,
true /* twoLevelTask */);
- // Do not move display to back because there is still another stack.
- stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask());
- verify(stack2).positionChildAtBottom(any(), eq(false) /* includingParents */);
+ // Do not move display to back because there is still another root task.
+ rootTask2.moveToBack("testMoveRootTaskToBackIncludingParent", rootTask2.getTopMostTask());
+ verify(rootTask2).positionChildAtBottom(any(), eq(false) /* includingParents */);
- // Also move display to back because there is only one stack left.
- taskDisplayArea.removeRootTask(stack1);
- stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask());
- verify(stack2).positionChildAtBottom(any(), eq(true) /* includingParents */);
+ // Also move display to back because there is only one root task left.
+ taskDisplayArea.removeRootTask(rootTask1);
+ rootTask2.moveToBack("testMoveRootTaskToBackIncludingParent", rootTask2.getTopMostTask());
+ verify(rootTask2).positionChildAtBottom(any(), eq(true) /* includingParents */);
}
@Test
public void testShouldBeVisible_Fullscreen() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- // Add an activity to the pinned stack so it isn't considered empty for visibility check.
+ // Add an activity to the pinned root task so it isn't considered empty for visibility
+ // check.
final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
- .setTask(pinnedStack)
+ .setTask(pinnedRootTask)
.build();
- assertTrue(homeStack.shouldBeVisible(null /* starting */));
- assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+ assertTrue(homeRootTask.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */));
- final Task fullscreenStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
- // should be visible since it is always on-top.
- doReturn(false).when(fullscreenStack).isTranslucent(any());
- assertFalse(homeStack.shouldBeVisible(null /* starting */));
- assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
- assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
+ final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ // Home root task shouldn't be visible behind an opaque fullscreen root task, but pinned
+ // root task should be visible since it is always on-top.
+ doReturn(false).when(fullscreenRootTask).isTranslucent(any());
+ assertFalse(homeRootTask.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */));
+ assertTrue(fullscreenRootTask.shouldBeVisible(null /* starting */));
- // Home stack should be visible behind a translucent fullscreen stack.
- doReturn(true).when(fullscreenStack).isTranslucent(any());
- assertTrue(homeStack.shouldBeVisible(null /* starting */));
- assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+ // Home root task should be visible behind a translucent fullscreen root task.
+ doReturn(true).when(fullscreenRootTask).isTranslucent(any());
+ assertTrue(homeRootTask.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */));
}
@Test
public void testShouldBeVisible_SplitScreen() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- // Home stack should always be fullscreen for this test.
- doReturn(false).when(homeStack).supportsSplitScreenWindowingMode();
- final Task splitScreenPrimary =
- createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ // Home root task should always be fullscreen for this test.
+ doReturn(false).when(homeRootTask).supportsSplitScreenWindowingMode();
+ final Task splitScreenPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final Task splitScreenSecondary =
- createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task splitScreenSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- // Home stack shouldn't be visible if both halves of split-screen are opaque.
+ // Home root task shouldn't be visible if both halves of split-screen are opaque.
doReturn(false).when(splitScreenPrimary).isTranslucent(any());
doReturn(false).when(splitScreenSecondary).isTranslucent(any());
- assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ assertFalse(homeRootTask.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_INVISIBLE, homeRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
splitScreenSecondary.getVisibility(null /* starting */));
- // Home stack should be visible if one of the halves of split-screen is translucent.
+ // Home root task should be visible if one of the halves of split-screen is translucent.
doReturn(true).when(splitScreenPrimary).isTranslucent(any());
- assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(homeRootTask.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
- homeStack.getVisibility(null /* starting */));
+ homeRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
splitScreenSecondary.getVisibility(null /* starting */));
- final Task splitScreenSecondary2 =
- createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task splitScreenSecondary2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// First split-screen secondary shouldn't be visible behind another opaque split-split
// secondary.
@@ -468,18 +640,17 @@
assertEquals(TASK_VISIBILITY_VISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
- final Task assistantStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT,
- true /* onTop */);
+ final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
- // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
- doReturn(false).when(assistantStack).isTranslucent(any());
- assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ // Split-screen root tasks shouldn't be visible behind an opaque fullscreen root task.
+ doReturn(false).when(assistantRootTask).isTranslucent(any());
+ assertTrue(assistantRootTask.shouldBeVisible(null /* starting */));
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
- assistantStack.getVisibility(null /* starting */));
+ assistantRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_INVISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_INVISIBLE,
@@ -487,14 +658,14 @@
assertEquals(TASK_VISIBILITY_INVISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
- // Split-screen stacks should be visible behind a translucent fullscreen stack.
- doReturn(true).when(assistantStack).isTranslucent(any());
- assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ // Split-screen root tasks should be visible behind a translucent fullscreen root task.
+ doReturn(true).when(assistantRootTask).isTranslucent(any());
+ assertTrue(assistantRootTask.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
- assistantStack.getVisibility(null /* starting */));
+ assistantRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitScreenPrimary.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
@@ -502,20 +673,20 @@
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitScreenSecondary2.getVisibility(null /* starting */));
- // Assistant stack shouldn't be visible behind translucent split-screen stack,
+ // Assistant root task shouldn't be visible behind translucent split-screen root task,
// unless it is configured to show on top of everything.
- doReturn(false).when(assistantStack).isTranslucent(any());
+ doReturn(false).when(assistantRootTask).isTranslucent(any());
doReturn(true).when(splitScreenPrimary).isTranslucent(any());
doReturn(true).when(splitScreenSecondary2).isTranslucent(any());
splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen");
splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen");
if (isAssistantOnTop()) {
- assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ assertTrue(assistantRootTask.shouldBeVisible(null /* starting */));
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
- assistantStack.getVisibility(null /* starting */));
+ assistantRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_INVISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_INVISIBLE,
@@ -523,11 +694,11 @@
assertEquals(TASK_VISIBILITY_INVISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
} else {
- assertFalse(assistantStack.shouldBeVisible(null /* starting */));
+ assertFalse(assistantRootTask.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
assertEquals(TASK_VISIBILITY_INVISIBLE,
- assistantStack.getVisibility(null /* starting */));
+ assistantRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_INVISIBLE,
@@ -539,35 +710,33 @@
@Test
public void testGetVisibility_MultiLevel() {
- final Task homeStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME,
- true /* onTop */);
- final Task splitPrimary = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
- final Task splitSecondary = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
- ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final Task splitPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
+ final Task splitSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
- doReturn(false).when(homeStack).isTranslucent(any());
+ doReturn(false).when(homeRootTask).isTranslucent(any());
doReturn(false).when(splitPrimary).isTranslucent(any());
doReturn(false).when(splitSecondary).isTranslucent(any());
// Re-parent home to split secondary.
- homeStack.reparent(splitSecondary, POSITION_TOP);
+ homeRootTask.reparent(splitSecondary, POSITION_TOP);
// Current tasks should be visible.
assertEquals(TASK_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */));
// Home task should still be visible even though it is a child of another visible task.
- assertEquals(TASK_VISIBILITY_VISIBLE, homeStack.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_VISIBLE, homeRootTask.getVisibility(null /* starting */));
// Add fullscreen translucent task that partially occludes split tasks
- final Task translucentStack = createStandardStackForVisibilityTest(
+ final Task translucentRootTask = createStandardRootTaskForVisibilityTest(
WINDOWING_MODE_FULLSCREEN, true /* translucent */);
// Fullscreen translucent task should be visible
- assertEquals(TASK_VISIBILITY_VISIBLE, translucentStack.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_VISIBLE,
+ translucentRootTask.getVisibility(null /* starting */));
// Split tasks should be visible behind translucent
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitPrimary.getVisibility(null /* starting */));
@@ -576,415 +745,400 @@
// Home task should be visible behind translucent since its parent is visible behind
// translucent.
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
- homeStack.getVisibility(null /* starting */));
+ homeRootTask.getVisibility(null /* starting */));
// Hide split-secondary
splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */);
// Home split secondary and home task should be invisible.
assertEquals(TASK_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_INVISIBLE, homeRootTask.getVisibility(null /* starting */));
}
@Test
public void testGetVisibility_FullscreenBehindTranslucent() {
- final Task bottomStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task bottomRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final Task translucentStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task translucentRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
- bottomStack.getVisibility(null /* starting */));
+ bottomRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
- translucentStack.getVisibility(null /* starting */));
+ translucentRootTask.getVisibility(null /* starting */));
}
@Test
public void testGetVisibility_FullscreenBehindTranslucentAndOpaque() {
- final Task bottomStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task bottomRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final Task translucentStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task translucentRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- final Task opaqueStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task opaqueRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- assertEquals(TASK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_INVISIBLE,
- translucentStack.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
+ translucentRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */));
}
@Test
public void testGetVisibility_FullscreenBehindOpaqueAndTranslucent() {
- final Task bottomStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task bottomRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final Task opaqueStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task opaqueRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final Task translucentStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task translucentRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- assertEquals(TASK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
- opaqueStack.getVisibility(null /* starting */));
+ opaqueRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
- translucentStack.getVisibility(null /* starting */));
+ translucentRootTask.getVisibility(null /* starting */));
}
@Test
public void testGetVisibility_FullscreenTranslucentBehindTranslucent() {
- final Task bottomTranslucentStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task bottomTranslucentRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- final Task translucentStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task translucentRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
- bottomTranslucentStack.getVisibility(null /* starting */));
+ bottomTranslucentRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
- translucentStack.getVisibility(null /* starting */));
+ translucentRootTask.getVisibility(null /* starting */));
}
@Test
public void testGetVisibility_FullscreenTranslucentBehindOpaque() {
- final Task bottomTranslucentStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task bottomTranslucentRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- final Task opaqueStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task opaqueRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
assertEquals(TASK_VISIBILITY_INVISIBLE,
- bottomTranslucentStack.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
+ bottomTranslucentRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */));
}
@Test
public void testGetVisibility_FullscreenBehindTranslucentAndPip() {
- final Task bottomStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task bottomRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- final Task translucentStack =
- createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+ final Task translucentRootTask =
+ createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
- bottomStack.getVisibility(null /* starting */));
+ bottomRootTask.getVisibility(null /* starting */));
assertEquals(TASK_VISIBILITY_VISIBLE,
- translucentStack.getVisibility(null /* starting */));
- // Add an activity to the pinned stack so it isn't considered empty for visibility check.
+ translucentRootTask.getVisibility(null /* starting */));
+ // Add an activity to the pinned root task so it isn't considered empty for visibility
+ // check.
final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
- .setTask(pinnedStack)
+ .setTask(pinnedRootTask)
.build();
- assertEquals(TASK_VISIBILITY_VISIBLE, pinnedStack.getVisibility(null /* starting */));
+ assertEquals(TASK_VISIBILITY_VISIBLE, pinnedRootTask.getVisibility(null /* starting */));
}
@Test
public void testShouldBeVisible_Finishing() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
+ ActivityRecord topRunningHomeActivity = homeRootTask.topRunningActivity();
if (topRunningHomeActivity == null) {
topRunningHomeActivity = new ActivityBuilder(mAtm)
- .setTask(homeStack)
+ .setTask(homeRootTask)
.build();
}
- final Task translucentStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- doReturn(true).when(translucentStack).isTranslucent(any());
+ final Task translucentRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ doReturn(true).when(translucentRootTask).isTranslucent(any());
- assertTrue(homeStack.shouldBeVisible(null /* starting */));
- assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+ assertTrue(homeRootTask.shouldBeVisible(null /* starting */));
+ assertTrue(translucentRootTask.shouldBeVisible(null /* starting */));
topRunningHomeActivity.finishing = true;
final ActivityRecord topRunningTranslucentActivity =
- translucentStack.topRunningActivity();
+ translucentRootTask.topRunningActivity();
topRunningTranslucentActivity.finishing = true;
- // Home stack should be visible even there are no running activities.
- assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ // Home root task should be visible even there are no running activities.
+ assertTrue(homeRootTask.shouldBeVisible(null /* starting */));
// Home should be visible if we are starting an activity within it.
- assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
- // The translucent stack shouldn't be visible since its activity marked as finishing.
- assertFalse(translucentStack.shouldBeVisible(null /* starting */));
+ assertTrue(homeRootTask.shouldBeVisible(topRunningHomeActivity /* starting */));
+ // The translucent root task shouldn't be visible since its activity marked as finishing.
+ assertFalse(translucentRootTask.shouldBeVisible(null /* starting */));
}
@Test
- public void testShouldBeVisible_FullscreenBehindTranslucentInHomeStack() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testShouldBeVisible_FullscreenBehindTranslucentInHomeRootTask() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setParentTask(homeStack)
- .setCreateTask(true)
- .build();
+ .setParentTask(homeRootTask)
+ .setCreateTask(true)
+ .build();
final Task task = firstActivity.getTask();
final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
.setTask(task)
.build();
doReturn(false).when(secondActivity).occludesParent();
- homeStack.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ homeRootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
false /* preserveWindows */);
assertTrue(firstActivity.shouldBeVisible());
}
@Test
- public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeBehindFullscreen() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final Task fullscreenStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
-
- doReturn(false).when(homeStack).isTranslucent(any());
- doReturn(false).when(fullscreenStack).isTranslucent(any());
-
- // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
- int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack);
- assertEquals(fullscreenStack, getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack);
- assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack));
- }
-
- @Test
- public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final Task fullscreenStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea,
+ final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- doReturn(false).when(homeStack).isTranslucent(any());
- doReturn(true).when(fullscreenStack).isTranslucent(any());
+ doReturn(false).when(homeRootTask).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask).isTranslucent(any());
- // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
- int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack);
- assertEquals(fullscreenStack, getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack);
- assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack));
+ // Ensure that we don't move the home root task if it is already behind the top fullscreen
+ // root task.
+ int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask);
+ assertEquals(fullscreenRootTask, getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask);
+ assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask));
}
@Test
- public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() {
- final Task fullscreenStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeBehindTranslucent() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- doReturn(false).when(homeStack).isTranslucent(any());
- doReturn(false).when(fullscreenStack).isTranslucent(any());
+ doReturn(false).when(homeRootTask).isTranslucent(any());
+ doReturn(true).when(fullscreenRootTask).isTranslucent(any());
- // Ensure we don't move the home stack if it is already on top
- int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack);
- assertNull(getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack);
- assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack));
+ // Ensure that we don't move the home root task if it is already behind the top fullscreen
+ // root task.
+ int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask);
+ assertEquals(fullscreenRootTask, getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask);
+ assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask));
}
@Test
- public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeOnTop() {
+ final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task fullscreenStack2 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+
+ doReturn(false).when(homeRootTask).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask).isTranslucent(any());
+
+ // Ensure we don't move the home root task if it is already on top
+ int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask);
+ assertNull(getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask);
+ assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask));
+ }
+
+ @Test
+ public void testMoveHomeRootTaskBehindBottomMostVisible_MoveHomeBehindFullscreen() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- doReturn(false).when(homeStack).isTranslucent(any());
- doReturn(false).when(fullscreenStack1).isTranslucent(any());
- doReturn(false).when(fullscreenStack2).isTranslucent(any());
+ doReturn(false).when(homeRootTask).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask1).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask2).isTranslucent(any());
- // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the
- // pinned stack
- assertEquals(fullscreenStack1, getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack);
- assertEquals(fullscreenStack2, getRootTaskAbove(homeStack));
+ // Ensure that we move the home root task behind the bottom most fullscreen root task,
+ // ignoring the pinned root task.
+ assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask);
+ assertEquals(fullscreenRootTask2, getRootTaskAbove(homeRootTask));
}
@Test
public void
- testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ testMoveHomeRootTaskBehindBottomMostVisible_MoveHomeBehindFullscreenAndTranslucent() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task fullscreenStack2 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
+ final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- doReturn(false).when(homeStack).isTranslucent(any());
- doReturn(false).when(fullscreenStack1).isTranslucent(any());
- doReturn(true).when(fullscreenStack2).isTranslucent(any());
+ doReturn(false).when(homeRootTask).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask1).isTranslucent(any());
+ doReturn(true).when(fullscreenRootTask2).isTranslucent(any());
- // Ensure that we move the home stack behind the bottom most non-translucent fullscreen
- // stack
- assertEquals(fullscreenStack1, getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack);
- assertEquals(fullscreenStack1, getRootTaskAbove(homeStack));
+ // Ensure that we move the home root task behind the bottom most non-translucent fullscreen
+ // root task.
+ assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask);
+ assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask));
}
@Test
- public void testMoveHomeStackBehindStack_BehindHomeStack() {
- final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task fullscreenStack2 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testMoveHomeRootTaskBehindRootTask_BehindHomeRootTask() {
+ final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- doReturn(false).when(homeStack).isTranslucent(any());
- doReturn(false).when(fullscreenStack1).isTranslucent(any());
- doReturn(false).when(fullscreenStack2).isTranslucent(any());
+ doReturn(false).when(homeRootTask).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask1).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask2).isTranslucent(any());
- // Ensure we don't move the home stack behind itself
- int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack);
- mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, homeStack);
- assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack));
+ // Ensure we don't move the home root task behind itself
+ int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask);
+ mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, homeRootTask);
+ assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask));
}
@Test
- public void testMoveHomeStackBehindStack() {
- final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task fullscreenStack2 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task fullscreenStack3 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task fullscreenStack4 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testMoveHomeRootTaskBehindRootTask() {
+ final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task fullscreenRootTask3 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task fullscreenRootTask4 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack1);
- assertEquals(fullscreenStack1, getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack2);
- assertEquals(fullscreenStack2, getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack4);
- assertEquals(fullscreenStack4, getRootTaskAbove(homeStack));
- mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack2);
- assertEquals(fullscreenStack2, getRootTaskAbove(homeStack));
+ mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask1);
+ assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask2);
+ assertEquals(fullscreenRootTask2, getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask4);
+ assertEquals(fullscreenRootTask4, getRootTaskAbove(homeRootTask));
+ mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask2);
+ assertEquals(fullscreenRootTask2, getRootTaskAbove(homeRootTask));
}
@Test
public void testSetAlwaysOnTop() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- assertEquals(pinnedStack, getRootTaskAbove(homeStack));
+ assertEquals(pinnedRootTask, getRootTaskAbove(homeRootTask));
- final Task alwaysOnTopStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- alwaysOnTopStack.setAlwaysOnTop(true);
- assertTrue(alwaysOnTopStack.isAlwaysOnTop());
- // Ensure (non-pinned) always on top stack is put below pinned stack.
- assertEquals(pinnedStack, getRootTaskAbove(alwaysOnTopStack));
+ final Task alwaysOnTopRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ alwaysOnTopRootTask.setAlwaysOnTop(true);
+ assertTrue(alwaysOnTopRootTask.isAlwaysOnTop());
+ // Ensure (non-pinned) always on top root task is put below pinned root task.
+ assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask));
- final Task nonAlwaysOnTopStack = createStackForShouldBeVisibleTest(
+ final Task nonAlwaysOnTopRootTask = createTaskForShouldBeVisibleTest(
mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- // Ensure non always on top stack is put below always on top stacks.
- assertEquals(alwaysOnTopStack, getRootTaskAbove(nonAlwaysOnTopStack));
+ // Ensure non always on top root task is put below always on top root tasks.
+ assertEquals(alwaysOnTopRootTask, getRootTaskAbove(nonAlwaysOnTopRootTask));
- final Task alwaysOnTopStack2 = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- alwaysOnTopStack2.setAlwaysOnTop(true);
- assertTrue(alwaysOnTopStack2.isAlwaysOnTop());
- // Ensure newly created always on top stack is placed above other all always on top stacks.
- assertEquals(pinnedStack, getRootTaskAbove(alwaysOnTopStack2));
+ final Task alwaysOnTopRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ alwaysOnTopRootTask2.setAlwaysOnTop(true);
+ assertTrue(alwaysOnTopRootTask2.isAlwaysOnTop());
+ // Ensure newly created always on top root task is placed above other all always on top
+ // root tasks.
+ assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask2));
- alwaysOnTopStack2.setAlwaysOnTop(false);
- // Ensure, when always on top is turned off for a stack, the stack is put just below all
- // other always on top stacks.
- assertEquals(alwaysOnTopStack, getRootTaskAbove(alwaysOnTopStack2));
- alwaysOnTopStack2.setAlwaysOnTop(true);
+ alwaysOnTopRootTask2.setAlwaysOnTop(false);
+ // Ensure, when always on top is turned off for a root task, the root task is put just below
+ // all other always on top root tasks.
+ assertEquals(alwaysOnTopRootTask, getRootTaskAbove(alwaysOnTopRootTask2));
+ alwaysOnTopRootTask2.setAlwaysOnTop(true);
// Ensure always on top state changes properly when windowing mode changes.
- alwaysOnTopStack2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertFalse(alwaysOnTopStack2.isAlwaysOnTop());
- assertEquals(alwaysOnTopStack, getRootTaskAbove(alwaysOnTopStack2));
- alwaysOnTopStack2.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertTrue(alwaysOnTopStack2.isAlwaysOnTop());
- assertEquals(pinnedStack, getRootTaskAbove(alwaysOnTopStack2));
+ alwaysOnTopRootTask2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertFalse(alwaysOnTopRootTask2.isAlwaysOnTop());
+ assertEquals(alwaysOnTopRootTask, getRootTaskAbove(alwaysOnTopRootTask2));
+ alwaysOnTopRootTask2.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertTrue(alwaysOnTopRootTask2.isAlwaysOnTop());
+ assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask2));
}
@Test
public void testSplitScreenMoveToFront() {
- final Task splitScreenPrimary = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final Task splitScreenSecondary = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
- ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final Task assistantStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT,
- true /* onTop */);
+ final Task splitScreenPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task splitScreenSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
doReturn(false).when(splitScreenPrimary).isTranslucent(any());
doReturn(false).when(splitScreenSecondary).isTranslucent(any());
- doReturn(false).when(assistantStack).isTranslucent(any());
+ doReturn(false).when(assistantRootTask).isTranslucent(any());
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
- assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ assertTrue(assistantRootTask.shouldBeVisible(null /* starting */));
splitScreenSecondary.moveToFront("testSplitScreenMoveToFront");
if (isAssistantOnTop()) {
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
- assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ assertTrue(assistantRootTask.shouldBeVisible(null /* starting */));
} else {
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
- assertFalse(assistantStack.shouldBeVisible(null /* starting */));
+ assertFalse(assistantRootTask.shouldBeVisible(null /* starting */));
}
}
- private Task createStandardStackForVisibilityTest(int windowingMode,
+ private Task createStandardRootTaskForVisibilityTest(int windowingMode,
boolean translucent) {
- final Task stack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ final Task rootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- doReturn(translucent).when(stack).isTranslucent(any());
- return stack;
+ doReturn(translucent).when(rootTask).isTranslucent(any());
+ return rootTask;
}
- private Task createStackForShouldBeVisibleTest(
+ private Task createTaskForShouldBeVisibleTest(
TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, boolean onTop) {
- return createStackForShouldBeVisibleTest(taskDisplayArea,
+ return createTaskForShouldBeVisibleTest(taskDisplayArea,
windowingMode, activityType, onTop, false /* twoLevelTask */);
}
@SuppressWarnings("TypeParameterUnusedInFormals")
- private Task createStackForShouldBeVisibleTest(TaskDisplayArea taskDisplayArea,
+ private Task createTaskForShouldBeVisibleTest(TaskDisplayArea taskDisplayArea,
int windowingMode, int activityType, boolean onTop, boolean twoLevelTask) {
final Task task;
if (activityType == ACTIVITY_TYPE_HOME) {
@@ -1157,20 +1311,20 @@
}
@Test
- public void testWontFinishHomeStackImmediately() {
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testWontFinishHomeRootTaskImmediately() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- ActivityRecord activity = homeStack.topRunningActivity();
+ ActivityRecord activity = homeRootTask.topRunningActivity();
if (activity == null) {
activity = new ActivityBuilder(mAtm)
- .setParentTask(homeStack)
+ .setParentTask(homeRootTask)
.setCreateTask(true)
.build();
}
- // Home stack should not be destroyed immediately.
- final ActivityRecord activity1 = finishTopActivity(homeStack);
+ // Home root task should not be destroyed immediately.
+ final ActivityRecord activity1 = finishTopActivity(homeRootTask);
assertEquals(FINISHING, activity1.getState());
}
@@ -1178,30 +1332,28 @@
public void testFinishCurrentActivity() {
// Create 2 activities on a new display.
final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
- final Task stack1 = createStackForShouldBeVisibleTest(
- display.getDefaultTaskDisplayArea(),
+ final Task rootTask1 = createTaskForShouldBeVisibleTest(display.getDefaultTaskDisplayArea(),
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final Task stack2 = createStackForShouldBeVisibleTest(
- display.getDefaultTaskDisplayArea(),
+ final Task rootTask2 = createTaskForShouldBeVisibleTest(display.getDefaultTaskDisplayArea(),
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- // There is still an activity1 in stack1 so the activity2 should be added to finishing list
- // that will be destroyed until idle.
- stack2.getTopNonFinishingActivity().mVisibleRequested = true;
- final ActivityRecord activity2 = finishTopActivity(stack2);
+ // There is still an activity1 in rootTask1 so the activity2 should be added to finishing
+ // list that will be destroyed until idle.
+ rootTask2.getTopNonFinishingActivity().mVisibleRequested = true;
+ final ActivityRecord activity2 = finishTopActivity(rootTask2);
assertEquals(STOPPING, activity2.getState());
assertThat(mSupervisor.mStoppingActivities).contains(activity2);
// The display becomes empty. Since there is no next activity to be idle, the activity
// should be destroyed immediately with updating configuration to restore original state.
- final ActivityRecord activity1 = finishTopActivity(stack1);
+ final ActivityRecord activity1 = finishTopActivity(rootTask1);
assertEquals(DESTROYING, activity1.getState());
verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
eq(display.mDisplayId), anyBoolean(), anyBoolean());
}
- private ActivityRecord finishTopActivity(Task stack) {
- final ActivityRecord activity = stack.topRunningActivity();
+ private ActivityRecord finishTopActivity(Task task) {
+ final ActivityRecord activity = task.topRunningActivity();
assertNotNull(activity);
activity.setState(STOPPED, "finishTopActivity");
activity.makeFinishingLocked();
@@ -1214,24 +1366,24 @@
// When focused activity and keyguard is going away, we should not sleep regardless
// of the display state, but keyguard-going-away should only take effects on default
// display since there is no keyguard on secondary displays (yet).
- verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
- verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
- // When not the focused stack, defer to display sleeping state.
- verifyShouldSleepActivities(false /* focusedStack */, true /*keyguardGoingAway*/,
+ // When not the focused root task, defer to display sleeping state.
+ verifyShouldSleepActivities(false /* focusedRootTask */, true /*keyguardGoingAway*/,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
// If keyguard is going away, defer to the display sleeping state.
- verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
- verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
}
@Test
- public void testStackOrderChangedOnRemoveStack() {
+ public void testRootTaskOrderChangedOnRemoveRootTask() {
final Task task = new TaskBuilder(mSupervisor).build();
RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
@@ -1244,7 +1396,7 @@
}
@Test
- public void testStackOrderChangedOnAddPositionStack() {
+ public void testRootTaskOrderChangedOnAddPositionRootTask() {
final Task task = new TaskBuilder(mSupervisor).build();
mDefaultTaskDisplayArea.removeRootTask(task);
@@ -1260,14 +1412,14 @@
}
@Test
- public void testStackOrderChangedOnPositionStack() {
+ public void testRootTaskOrderChangedOnPositionRootTask() {
RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
try {
- final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
+ final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(
mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
- mDefaultTaskDisplayArea.positionChildAt(POSITION_BOTTOM, fullscreenStack1,
+ mDefaultTaskDisplayArea.positionChildAt(POSITION_BOTTOM, fullscreenRootTask1,
false /*includingParents*/);
} finally {
mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener);
@@ -1443,7 +1595,7 @@
com.android.internal.R.bool.config_assistantOnTopOfDream);
}
- private void verifyShouldSleepActivities(boolean focusedStack,
+ private void verifyShouldSleepActivities(boolean focusedRootTask,
boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
boolean expected) {
final Task task = new TaskBuilder(mSupervisor).build();
@@ -1454,13 +1606,13 @@
task.mDisplayContent = display;
doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
doReturn(displaySleeping).when(display).isSleeping();
- doReturn(focusedStack).when(task).isFocusedRootTaskOnDisplay();
+ doReturn(focusedRootTask).when(task).isFocusedRootTaskOnDisplay();
assertEquals(expected, task.shouldSleepActivities());
}
private static class RootTaskOrderChangedListener
- implements OnRootTaskOrderChangedListener {
+ implements TaskDisplayArea.OnRootTaskOrderChangedListener {
public boolean mChanged = false;
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 1b114c1..20bced2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -16,42 +16,93 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.TYPE_VIRTUAL;
+import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
import static com.android.server.wm.Task.ActivityState.FINISHING;
import static com.android.server.wm.Task.ActivityState.PAUSED;
import static com.android.server.wm.Task.ActivityState.PAUSING;
import static com.android.server.wm.Task.ActivityState.STOPPED;
import static com.android.server.wm.Task.ActivityState.STOPPING;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
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;
+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.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.refEq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import android.app.ActivityOptions;
import android.app.WindowConfiguration;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
+import android.util.Pair;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
+import com.android.internal.app.ResolverActivity;
+
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+
/**
- * Tests for RootWindowContainer.
+ * Tests for {@link RootWindowContainer}.
*
* Build/Install/Run:
* atest WmTests:RootWindowContainerTests
*/
-@SmallTest
+@MediumTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class RootWindowContainerTests extends WindowTestsBase {
+ @Before
+ public void setUp() throws Exception {
+ doNothing().when(mAtm).updateSleepIfNeededLocked();
+ }
+
@Test
public void testUpdateDefaultDisplayWindowingModeOnSettingsRetrieved() {
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
@@ -166,5 +217,860 @@
assertFalse(task.hasChild());
assertFalse(wpc.hasActivities());
}
+
+ /**
+ * This test ensures that we do not try to restore a task based off an invalid task id. We
+ * should expect {@code null} to be returned in this case.
+ */
+ @Test
+ public void testRestoringInvalidTask() {
+ mRootWindowContainer.getDefaultDisplay().removeAllTasks();
+ Task task = mRootWindowContainer.anyTaskForId(0 /*taskId*/,
+ MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
+ assertNull(task);
+ }
+
+ /**
+ * This test ensures that an existing task in the pinned root task is moved to the fullscreen
+ * activity root task when a new task is added.
+ */
+ @Test
+ public void testReplacingTaskInPinnedRootTask() {
+ Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
+ .setTask(fullscreenTask).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
+ .setTask(fullscreenTask).build();
+
+ fullscreenTask.moveToFront("testReplacingTaskInPinnedRootTask");
+
+ // Ensure full screen root task has both tasks.
+ ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
+
+ // Move first activity to pinned root task.
+ mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove");
+
+ final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
+ Task pinnedRootTask = taskDisplayArea.getRootPinnedTask();
+ // Ensure a task has moved over.
+ ensureTaskPlacement(pinnedRootTask, firstActivity);
+ ensureTaskPlacement(fullscreenTask, secondActivity);
+
+ // Move second activity to pinned root task.
+ mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove");
+
+ // Need to get root tasks again as a new instance might have been created.
+ pinnedRootTask = taskDisplayArea.getRootPinnedTask();
+ fullscreenTask = taskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
+ // Ensure root tasks have swapped tasks.
+ ensureTaskPlacement(pinnedRootTask, secondActivity);
+ ensureTaskPlacement(fullscreenTask, firstActivity);
+ }
+
+ @Test
+ public void testMovingBottomMostRootTaskActivityToPinnedRootTask() {
+ final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
+ .setTask(fullscreenTask).build();
+ final Task task = firstActivity.getTask();
+
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
+ .setTask(fullscreenTask).build();
+
+ fullscreenTask.moveTaskToBack(task);
+
+ // Ensure full screen task has both tasks.
+ ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
+ assertEquals(task.getTopMostActivity(), secondActivity);
+ firstActivity.setState(STOPPED, "testMovingBottomMostRootTaskActivityToPinnedRootTask");
+
+
+ // Move first activity to pinned root task.
+ mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
+
+ assertTrue(firstActivity.mRequestForceTransition);
+ }
+
+ private static void ensureTaskPlacement(Task task, ActivityRecord... activities) {
+ final ArrayList<ActivityRecord> taskActivities = new ArrayList<>();
+
+ task.forAllActivities((Consumer<ActivityRecord>) taskActivities::add, false);
+
+ assertEquals("Expecting " + Arrays.deepToString(activities) + " got " + taskActivities,
+ taskActivities.size(), activities != null ? activities.length : 0);
+
+ if (activities == null) {
+ return;
+ }
+
+ for (ActivityRecord activity : activities) {
+ assertTrue(taskActivities.contains(activity));
+ }
+ }
+
+ @Test
+ public void testApplySleepTokens() {
+ final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
+ final KeyguardController keyguard = mSupervisor.getKeyguardController();
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(display)
+ .setOnTop(false)
+ .build();
+
+ // Make sure we wake and resume in the case the display is turning on and the keyguard is
+ // not showing.
+ verifySleepTokenBehavior(display, keyguard, task, true /*displaySleeping*/,
+ false /* displayShouldSleep */, true /* isFocusedTask */,
+ false /* keyguardShowing */, true /* expectWakeFromSleep */,
+ true /* expectResumeTopActivity */);
+
+ // Make sure we wake and don't resume when the display is turning on and the keyguard is
+ // showing.
+ verifySleepTokenBehavior(display, keyguard, task, true /*displaySleeping*/,
+ false /* displayShouldSleep */, true /* isFocusedTask */,
+ true /* keyguardShowing */, true /* expectWakeFromSleep */,
+ false /* expectResumeTopActivity */);
+
+ // Make sure we wake and don't resume when the display is turning on and the keyguard is
+ // not showing as unfocused.
+ verifySleepTokenBehavior(display, keyguard, task, true /*displaySleeping*/,
+ false /* displayShouldSleep */, false /* isFocusedTask */,
+ false /* keyguardShowing */, true /* expectWakeFromSleep */,
+ false /* expectResumeTopActivity */);
+
+ // Should not do anything if the display state hasn't changed.
+ verifySleepTokenBehavior(display, keyguard, task, false /*displaySleeping*/,
+ false /* displayShouldSleep */, true /* isFocusedTask */,
+ false /* keyguardShowing */, false /* expectWakeFromSleep */,
+ false /* expectResumeTopActivity */);
+ }
+
+ private void verifySleepTokenBehavior(DisplayContent display, KeyguardController keyguard,
+ Task task, boolean displaySleeping, boolean displayShouldSleep,
+ boolean isFocusedTask, boolean keyguardShowing, boolean expectWakeFromSleep,
+ boolean expectResumeTopActivity) {
+ reset(task);
+
+ doReturn(displayShouldSleep).when(display).shouldSleep();
+ doReturn(displaySleeping).when(display).isSleeping();
+ doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt());
+
+ doReturn(isFocusedTask).when(task).isFocusedRootTaskOnDisplay();
+ doReturn(isFocusedTask ? task : null).when(display).getFocusedRootTask();
+ TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea();
+ doReturn(isFocusedTask ? task : null).when(defaultTaskDisplayArea).getFocusedRootTask();
+ mRootWindowContainer.applySleepTokens(true);
+ verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked();
+ verify(task, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked(
+ null /* target */, null /* targetOptions */);
+ }
+
+ @Test
+ public void testAwakeFromSleepingWithAppConfiguration() {
+ final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.moveFocusableActivityToTop("test");
+ assertTrue(activity.getRootTask().isFocusedRootTaskOnDisplay());
+ ActivityRecordTests.setRotatedScreenOrientationSilently(activity);
+
+ final Configuration rotatedConfig = new Configuration();
+ display.computeScreenConfiguration(rotatedConfig, display.getDisplayRotation()
+ .rotationForOrientation(activity.getOrientation(), display.getRotation()));
+ assertNotEquals(activity.getConfiguration().orientation, rotatedConfig.orientation);
+ // Assume the activity was shown in different orientation. For example, the top activity is
+ // landscape and the portrait lockscreen is shown.
+ activity.setLastReportedConfiguration(
+ new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
+ activity.setState(Task.ActivityState.STOPPED, "sleep");
+
+ display.setIsSleeping(true);
+ doReturn(false).when(display).shouldSleep();
+ // Allow to resume when awaking.
+ setBooted(mAtm);
+ mRootWindowContainer.applySleepTokens(true);
+
+ // The display orientation should be changed by the activity so there is no relaunch.
+ verify(activity, never()).relaunchActivityLocked(anyBoolean());
+ assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation);
+ }
+
+ /**
+ * Verifies that removal of activity with task and root task is done correctly.
+ */
+ @Test
+ public void testRemovingRootTaskOnAppCrash() {
+ final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+ .getDefaultTaskDisplayArea();
+ final int originalRootTaskCount = defaultTaskDisplayArea.getRootTaskCount();
+ final Task rootTask = defaultTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(rootTask).build();
+
+ assertEquals(originalRootTaskCount + 1, defaultTaskDisplayArea.getRootTaskCount());
+
+ // Let's pretend that the app has crashed.
+ firstActivity.app.setThread(null);
+ mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test");
+
+ // Verify that the root task was removed.
+ assertEquals(originalRootTaskCount, defaultTaskDisplayArea.getRootTaskCount());
+ }
+
+ /**
+ * Verifies that removal of activities with task and root task is done correctly when there are
+ * several task display areas.
+ */
+ @Test
+ public void testRemovingRootTaskOnAppCrash_multipleDisplayAreas() {
+ final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+ .getDefaultTaskDisplayArea();
+ final int originalRootTaskCount = defaultTaskDisplayArea.getRootTaskCount();
+ final Task rootTask = defaultTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(rootTask).build();
+ assertEquals(originalRootTaskCount + 1, defaultTaskDisplayArea.getRootTaskCount());
+
+ final DisplayContent dc = defaultTaskDisplayArea.getDisplayContent();
+ final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
+ dc, mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST);
+ final Task secondRootTask = secondTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ new ActivityBuilder(mAtm).setTask(secondRootTask).setUseProcess(firstActivity.app).build();
+ assertEquals(1, secondTaskDisplayArea.getRootTaskCount());
+
+ // Let's pretend that the app has crashed.
+ firstActivity.app.setThread(null);
+ mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test");
+
+ // Verify that the root tasks were removed.
+ assertEquals(originalRootTaskCount, defaultTaskDisplayArea.getRootTaskCount());
+ assertEquals(0, secondTaskDisplayArea.getRootTaskCount());
+ }
+
+ @Test
+ public void testFocusability() {
+ final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+ .getDefaultTaskDisplayArea();
+ final Task task = defaultTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
+
+ // Created tasks are focusable by default.
+ assertTrue(task.isTopActivityFocusable());
+ assertTrue(activity.isFocusable());
+
+ // If the task is made unfocusable, its activities should inherit that.
+ task.setFocusable(false);
+ assertFalse(task.isTopActivityFocusable());
+ assertFalse(activity.isFocusable());
+
+ final Task pinnedTask = defaultTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
+ .setTask(pinnedTask).build();
+
+ // We should not be focusable when in pinned mode
+ assertFalse(pinnedTask.isTopActivityFocusable());
+ assertFalse(pinnedActivity.isFocusable());
+
+ // Add flag forcing focusability.
+ pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
+
+ // Task with FLAG_ALWAYS_FOCUSABLE should be focusable.
+ assertTrue(pinnedTask.isTopActivityFocusable());
+ assertTrue(pinnedActivity.isFocusable());
+ }
+
+ /**
+ * Verify that split-screen primary root task will be chosen if activity is launched that
+ * targets split-screen secondary, but a matching existing instance is found on top of
+ * split-screen primary root task.
+ */
+ @Test
+ public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() {
+ // Create primary split-screen root task with a task and an activity.
+ final Task primaryRootTask = mRootWindowContainer.getDefaultTaskDisplayArea()
+ .createRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ final Task task = new TaskBuilder(mSupervisor).setParentTask(primaryRootTask).build();
+ final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build();
+
+ // Find a launch root task for the top activity in split-screen primary, while requesting
+ // split-screen secondary.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ final Task result =
+ mRootWindowContainer.getLaunchRootTask(r, options, task, true /* onTop */);
+
+ // Assert that the primary root task is returned.
+ assertEquals(primaryRootTask, result);
+ }
+
+ /**
+ * Verify that home root task would be moved to front when the top activity is Recents.
+ */
+ @Test
+ public void testFindTaskToMoveToFrontWhenRecentsOnTop() {
+ // Create root task/task on default display.
+ final Task targetRootTask = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setOnTop(false)
+ .build();
+ final Task targetTask = targetRootTask.getBottomMostTask();
+
+ // Create Recents on top of the display.
+ final Task rootTask = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setActivityType(ACTIVITY_TYPE_RECENTS)
+ .build();
+
+ final String reason = "findTaskToMoveToFront";
+ mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
+ false);
+
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ verify(taskDisplayArea).moveHomeRootTaskToFront(contains(reason));
+ }
+
+ /**
+ * Verify that home root task won't be moved to front if the top activity on other display is
+ * Recents.
+ */
+ @Test
+ public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() {
+ // Create tasks on default display.
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ final Task targetRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final Task targetTask = new TaskBuilder(mSupervisor).setParentTask(targetRootTask).build();
+
+ // Create Recents on secondary display.
+ final TestDisplayContent secondDisplay = addNewDisplayContentAt(
+ DisplayContent.POSITION_TOP);
+ final Task rootTask = secondDisplay.getDefaultTaskDisplayArea()
+ .createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */);
+ new ActivityBuilder(mAtm).setTask(rootTask).build();
+
+ final String reason = "findTaskToMoveToFront";
+ mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
+ false);
+
+ verify(taskDisplayArea, never()).moveHomeRootTaskToFront(contains(reason));
+ }
+
+ /**
+ * Verify if a root task is not at the topmost position, it should be able to resume its
+ * activity if the root task is the top focused.
+ */
+ @Test
+ public void testResumeActivityWhenNonTopmostRootTaskIsTopFocused() {
+ // Create a root task at bottom.
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, false /* onTop */));
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
+ taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/);
+
+ // Assume the task is not at the topmost position (e.g. behind always-on-top root tasks)
+ // but it is the current top focused task.
+ assertFalse(rootTask.isTopRootTaskInDisplayArea());
+ doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
+
+ // Use the task as target to resume.
+ mRootWindowContainer.resumeFocusedTasksTopActivities(
+ rootTask, activity, null /* targetOptions */);
+
+ // Verify the target task should resume its activity.
+ verify(rootTask, times(1)).resumeTopActivityUncheckedLocked(
+ eq(activity), eq(null /* targetOptions */));
+ }
+
+ /**
+ * Verify that home activity will be started on a display even if another display has a
+ * focusable activity.
+ */
+ @Test
+ public void testResumeFocusedRootTasksStartsHomeActivity_NoActivities() {
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ taskDisplayArea.getRootHomeTask().removeIfPossible();
+ taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+
+ doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
+
+ mAtm.setBooted(true);
+
+ // Trigger resume on all displays
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+
+ // Verify that home activity was started on the default display
+ verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea));
+ }
+
+ /**
+ * Verify that home activity will be started on a display even if another display has a
+ * focusable activity.
+ */
+ @Test
+ public void testResumeFocusedRootTasksStartsHomeActivity_ActivityOnSecondaryScreen() {
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ taskDisplayArea.getRootHomeTask().removeIfPossible();
+ taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+
+ // Create an activity on secondary display.
+ final TestDisplayContent secondDisplay = addNewDisplayContentAt(
+ DisplayContent.POSITION_TOP);
+ final Task rootTask = secondDisplay.getDefaultTaskDisplayArea().createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ new ActivityBuilder(mAtm).setTask(rootTask).build();
+
+ doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
+
+ mAtm.setBooted(true);
+
+ // Trigger resume on all displays
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+
+ // Verify that home activity was started on the default display
+ verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea));
+ }
+
+ /**
+ * Verify that a lingering transition is being executed in case the activity to be resumed is
+ * already resumed
+ */
+ @Test
+ public void testResumeActivityLingeringTransition() {
+ // Create a root task at top.
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, false /* onTop */));
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(rootTask).setOnTop(true).build();
+ activity.setState(Task.ActivityState.RESUMED, "test");
+
+ // Assume the task is at the topmost position
+ assertTrue(rootTask.isTopRootTaskInDisplayArea());
+
+ // Use the task as target to resume.
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+
+ // Verify the lingering app transition is being executed because it's already resumed
+ verify(rootTask, times(1)).executeAppTransition(any());
+ }
+
+ @Test
+ public void testResumeActivityLingeringTransition_notExecuted() {
+ // Create a root task at bottom.
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, false /* onTop */));
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(rootTask).setOnTop(true).build();
+ activity.setState(Task.ActivityState.RESUMED, "test");
+ taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/);
+
+ // Assume the task is at the topmost position
+ assertFalse(rootTask.isTopRootTaskInDisplayArea());
+ doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
+
+ // Use the task as target to resume.
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+
+ // Verify the lingering app transition is being executed because it's already resumed
+ verify(rootTask, never()).executeAppTransition(any());
+ }
+
+ /**
+ * Tests that home activities can be started on the displays that supports system decorations.
+ */
+ @Test
+ public void testStartHomeOnAllDisplays() {
+ mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+ mockResolveSecondaryHomeActivity();
+
+ // Create secondary displays.
+ final TestDisplayContent secondDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 1500)
+ .setSystemDecorations(true).build();
+
+ doReturn(true).when(mRootWindowContainer)
+ .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean());
+ doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+ anyBoolean());
+
+ mRootWindowContainer.startHomeOnAllDisplays(0, "testStartHome");
+
+ assertTrue(mRootWindowContainer.getDefaultDisplay().getTopRootTask().isActivityTypeHome());
+ assertNotNull(secondDisplay.getTopRootTask());
+ assertTrue(secondDisplay.getTopRootTask().isActivityTypeHome());
+ }
+
+ /**
+ * Tests that home activities won't be started before booting when display added.
+ */
+ @Test
+ public void testNotStartHomeBeforeBoot() {
+ final int displayId = 1;
+ final boolean isBooting = mAtm.mAmInternal.isBooting();
+ final boolean isBooted = mAtm.mAmInternal.isBooted();
+ try {
+ mAtm.mAmInternal.setBooting(false);
+ mAtm.mAmInternal.setBooted(false);
+ mRootWindowContainer.onDisplayAdded(displayId);
+ verify(mRootWindowContainer, never()).startHomeOnDisplay(anyInt(), any(), anyInt());
+ } finally {
+ mAtm.mAmInternal.setBooting(isBooting);
+ mAtm.mAmInternal.setBooted(isBooted);
+ }
+ }
+
+ /**
+ * Tests whether home can be started if being instrumented.
+ */
+ @Test
+ public void testCanStartHomeWhenInstrumented() {
+ final ActivityInfo info = new ActivityInfo();
+ info.applicationInfo = new ApplicationInfo();
+ final WindowProcessController app = mock(WindowProcessController.class);
+ doReturn(app).when(mAtm).getProcessController(any(), anyInt());
+
+ // Can not start home if we don't want to start home while home is being instrumented.
+ doReturn(true).when(app).isInstrumenting();
+ final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+ .getDefaultTaskDisplayArea();
+ assertFalse(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
+ false /* allowInstrumenting*/));
+
+ // Can start home for other cases.
+ assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
+ true /* allowInstrumenting*/));
+
+ doReturn(false).when(app).isInstrumenting();
+ assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
+ false /* allowInstrumenting*/));
+ assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea,
+ true /* allowInstrumenting*/));
+ }
+
+ /**
+ * Tests that secondary home activity should not be resolved if device is still locked.
+ */
+ @Test
+ public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() {
+ // Create secondary displays.
+ final TestDisplayContent secondDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 1500)
+ .setSystemDecorations(true).build();
+
+ // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
+ final int currentUser = mRootWindowContainer.mCurrentUser;
+ mRootWindowContainer.mCurrentUser = -1;
+
+ mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome",
+ secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */);
+
+ try {
+ verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any());
+ } finally {
+ mRootWindowContainer.mCurrentUser = currentUser;
+ }
+ }
+
+ /**
+ * Tests that secondary home activity should not be resolved if display does not support system
+ * decorations.
+ */
+ @Test
+ public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() {
+ // Create secondary displays.
+ final TestDisplayContent secondDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 1500)
+ .setSystemDecorations(false).build();
+
+ mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome",
+ secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */);
+
+ verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any());
+ }
+
+ /**
+ * Tests that when starting {@link #ResolverActivity} for home, it should use the standard
+ * activity type (in a new root task) so the order of back stack won't be broken.
+ */
+ @Test
+ public void testStartResolverActivityForHome() {
+ final ActivityInfo info = new ActivityInfo();
+ info.applicationInfo = new ApplicationInfo();
+ info.applicationInfo.packageName = "android";
+ info.name = ResolverActivity.class.getName();
+ doReturn(info).when(mRootWindowContainer).resolveHomeActivity(anyInt(), any());
+
+ mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "test", DEFAULT_DISPLAY);
+ final ActivityRecord resolverActivity = mRootWindowContainer.topRunningActivity();
+
+ assertEquals(info, resolverActivity.info);
+ assertEquals(ACTIVITY_TYPE_STANDARD, resolverActivity.getRootTask().getActivityType());
+ }
+
+ /**
+ * Tests that secondary home should be selected if primary home not set.
+ */
+ @Test
+ public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSet() {
+ // Setup: primary home not set.
+ final Intent primaryHomeIntent = mAtm.getHomeIntent();
+ final ActivityInfo aInfoPrimary = new ActivityInfo();
+ aInfoPrimary.name = ResolverActivity.class.getName();
+ doReturn(aInfoPrimary).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
+ refEq(primaryHomeIntent));
+ // Setup: set secondary home.
+ mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */);
+
+ // Run the test.
+ final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
+ .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
+ final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
+ assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
+ assertEquals(aInfoSecondary.applicationInfo.packageName,
+ resolvedInfo.first.applicationInfo.packageName);
+ }
+
+ /**
+ * Tests that the default secondary home activity is always picked when it is in forced by
+ * config_useSystemProvidedLauncherForSecondary.
+ */
+ @Test
+ public void testResolveSecondaryHomeActivityForced() {
+ // SetUp: set primary home.
+ mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+ // SetUp: set secondary home and force it.
+ mockResolveHomeActivity(false /* primaryHome */, true /* forceSystemProvided */);
+ final Intent secondaryHomeIntent =
+ mAtm.getSecondaryHomeIntent(null /* preferredPackage */);
+ final List<ResolveInfo> resolutions = new ArrayList<>();
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
+ resolveInfo.activityInfo = aInfoSecondary;
+ resolutions.add(resolveInfo);
+ doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(),
+ refEq(secondaryHomeIntent));
+ doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+ anyBoolean());
+
+ // Run the test.
+ final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
+ .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
+ assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
+ assertEquals(aInfoSecondary.applicationInfo.packageName,
+ resolvedInfo.first.applicationInfo.packageName);
+ }
+
+ /**
+ * Tests that secondary home should be selected if primary home not support secondary displays
+ * or there is no matched activity in the same package as selected primary home.
+ */
+ @Test
+ public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSupportMultiDisplay() {
+ // Setup: there is no matched activity in the same package as selected primary home.
+ mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+ final List<ResolveInfo> resolutions = new ArrayList<>();
+ doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
+ // Setup: set secondary home.
+ mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */);
+
+ // Run the test.
+ final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
+ .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
+ final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
+ assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
+ assertEquals(aInfoSecondary.applicationInfo.packageName,
+ resolvedInfo.first.applicationInfo.packageName);
+ }
+ /**
+ * Tests that primary home activity should be selected if it already support secondary displays.
+ */
+ @Test
+ public void testResolveSecondaryHomeActivityWhenPrimaryHomeSupportMultiDisplay() {
+ // SetUp: set primary home.
+ mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+ // SetUp: put primary home info on 2nd item
+ final List<ResolveInfo> resolutions = new ArrayList<>();
+ final ResolveInfo infoFake1 = new ResolveInfo();
+ infoFake1.activityInfo = new ActivityInfo();
+ infoFake1.activityInfo.name = "fakeActivity1";
+ infoFake1.activityInfo.applicationInfo = new ApplicationInfo();
+ infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1";
+ final ResolveInfo infoFake2 = new ResolveInfo();
+ final ActivityInfo aInfoPrimary = getFakeHomeActivityInfo(true /* primaryHome */);
+ infoFake2.activityInfo = aInfoPrimary;
+ resolutions.add(infoFake1);
+ resolutions.add(infoFake2);
+ doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
+
+ doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+ anyBoolean());
+
+ // Run the test.
+ final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
+ .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
+ assertEquals(aInfoPrimary.name, resolvedInfo.first.name);
+ assertEquals(aInfoPrimary.applicationInfo.packageName,
+ resolvedInfo.first.applicationInfo.packageName);
+ }
+
+ /**
+ * Tests that the first one that matches should be selected if there are multiple activities.
+ */
+ @Test
+ public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() {
+ // SetUp: set primary home.
+ mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+ // Setup: prepare two eligible activity info.
+ final List<ResolveInfo> resolutions = new ArrayList<>();
+ final ResolveInfo infoFake1 = new ResolveInfo();
+ infoFake1.activityInfo = new ActivityInfo();
+ infoFake1.activityInfo.name = "fakeActivity1";
+ infoFake1.activityInfo.applicationInfo = new ApplicationInfo();
+ infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1";
+ final ResolveInfo infoFake2 = new ResolveInfo();
+ infoFake2.activityInfo = new ActivityInfo();
+ infoFake2.activityInfo.name = "fakeActivity2";
+ infoFake2.activityInfo.applicationInfo = new ApplicationInfo();
+ infoFake2.activityInfo.applicationInfo.packageName = "fakePackage2";
+ resolutions.add(infoFake1);
+ resolutions.add(infoFake2);
+ doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
+
+ doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
+ anyBoolean());
+
+ // Use the first one of matched activities in the same package as selected primary home.
+ final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
+ .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class));
+
+ assertEquals(infoFake1.activityInfo.applicationInfo.packageName,
+ resolvedInfo.first.applicationInfo.packageName);
+ assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name);
+ }
+
+ /**
+ * Test that {@link RootWindowContainer#getLaunchRootTask} with the real caller id will get the
+ * expected root task when requesting the activity launch on the secondary display.
+ */
+ @Test
+ public void testGetLaunchRootTaskWithRealCallerId() {
+ // Create a non-system owned virtual display.
+ final TestDisplayContent secondaryDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 1500)
+ .setType(TYPE_VIRTUAL).setOwnerUid(100).build();
+
+ // Create an activity with specify the original launch pid / uid.
+ final ActivityRecord r = new ActivityBuilder(mAtm).setLaunchedFromPid(200)
+ .setLaunchedFromUid(200).build();
+
+ // Simulate ActivityStarter to find a launch root task for requesting the activity to launch
+ // on the secondary display with realCallerId.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(secondaryDisplay.mDisplayId);
+ options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId,
+ 300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info);
+ final Task result = mRootWindowContainer.getLaunchRootTask(r, options,
+ null /* task */, true /* onTop */, null, 300 /* test realCallerPid */,
+ 300 /* test realCallerUid */);
+
+ // Assert that the root task is returned as expected.
+ assertNotNull(result);
+ assertEquals("The display ID of the root task should same as secondary display ",
+ secondaryDisplay.mDisplayId, result.getDisplayId());
+ }
+
+ @Test
+ public void testGetValidLaunchRootTaskOnDisplayWithCandidateRootTask() {
+ // Create a root task with an activity on secondary display.
+ final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mAtm, 300,
+ 600).build();
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(secondaryDisplay).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
+
+ // Make sure the root task is valid and can be reused on default display.
+ final Task rootTask = mRootWindowContainer.getValidLaunchRootTaskInTaskDisplayArea(
+ mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task,
+ null /* options */, null /* launchParams */);
+ assertEquals(task, rootTask);
+ }
+
+ @Test
+ public void testSwitchUser_missingHomeRootTask() {
+ final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ doReturn(fullscreenTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
+
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ Task rootHomeTask = taskDisplayArea.getRootHomeTask();
+ if (rootHomeTask != null) {
+ rootHomeTask.removeImmediately();
+ }
+ assertNull(taskDisplayArea.getRootHomeTask());
+
+ int currentUser = mRootWindowContainer.mCurrentUser;
+ int otherUser = currentUser + 1;
+
+ mRootWindowContainer.switchUser(otherUser, null);
+
+ assertNotNull(taskDisplayArea.getRootHomeTask());
+ assertEquals(taskDisplayArea.getTopRootTask(), taskDisplayArea.getRootHomeTask());
+ }
+
+ /**
+ * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
+ * info for test cases.
+ *
+ * @param primaryHome Indicate to use primary home intent as parameter, otherwise, use
+ * secondary home intent.
+ * @param forceSystemProvided Indicate to force using system provided home activity.
+ */
+ private void mockResolveHomeActivity(boolean primaryHome, boolean forceSystemProvided) {
+ ActivityInfo targetActivityInfo = getFakeHomeActivityInfo(primaryHome);
+ Intent targetIntent;
+ if (primaryHome) {
+ targetIntent = mAtm.getHomeIntent();
+ } else {
+ Resources resources = mContext.getResources();
+ spyOn(resources);
+ doReturn(targetActivityInfo.applicationInfo.packageName).when(resources).getString(
+ com.android.internal.R.string.config_secondaryHomePackage);
+ doReturn(forceSystemProvided).when(resources).getBoolean(
+ com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
+ targetIntent = mAtm.getSecondaryHomeIntent(null /* preferredPackage */);
+ }
+ doReturn(targetActivityInfo).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
+ refEq(targetIntent));
+ }
+
+ /**
+ * Mock {@link RootWindowContainer#resolveSecondaryHomeActivity} for returning consistent
+ * activity info for test cases.
+ */
+ private void mockResolveSecondaryHomeActivity() {
+ final Intent secondaryHomeIntent = mAtm
+ .getSecondaryHomeIntent(null /* preferredPackage */);
+ final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false);
+ doReturn(Pair.create(aInfoSecondary, secondaryHomeIntent)).when(mRootWindowContainer)
+ .resolveSecondaryHomeActivity(anyInt(), any());
+ }
+
+ private ActivityInfo getFakeHomeActivityInfo(boolean primaryHome) {
+ final ActivityInfo aInfo = new ActivityInfo();
+ aInfo.name = primaryHome ? "fakeHomeActivity" : "fakeSecondaryHomeActivity";
+ aInfo.applicationInfo = new ApplicationInfo();
+ aInfo.applicationInfo.packageName =
+ primaryHome ? "fakeHomePackage" : "fakeSecondaryHomePackage";
+ return aInfo;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index eba5634..92d4ede 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -28,6 +28,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -35,13 +36,16 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.Task.ActivityState.RESUMED;
+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.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -56,8 +60,6 @@
import com.android.server.wm.LaunchParamsController.LaunchParams;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -72,35 +74,17 @@
@RunWith(WindowTestRunner.class)
public class TaskDisplayAreaTests extends WindowTestsBase {
- private Task mPinnedTask;
-
- @Before
- public void setUp() throws Exception {
- mPinnedTask = createTaskStackOnDisplay(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
- // Stack should contain visible app window to be considered visible.
- assertFalse(mPinnedTask.isVisible());
- final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent);
- mPinnedTask.addChild(pinnedApp, 0 /* addPos */);
- assertTrue(mPinnedTask.isVisible());
- }
-
- @After
- public void tearDown() throws Exception {
- mPinnedTask.removeImmediately();
- }
-
@Test
public void getOrCreateLaunchRootRespectsResolvedWindowingMode() {
- final Task rootTask = createTaskStackOnDisplay(
- WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task rootTask = createTask(
+ mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
rootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
taskDisplayArea.setLaunchRootTask(
rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD});
- final Task candidateRootTask = createTaskStackOnDisplay(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task candidateRootTask = createTask(
+ mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
final LaunchParams launchParams = new LaunchParams();
launchParams.mWindowingMode = WINDOWING_MODE_FREEFORM;
@@ -113,15 +97,15 @@
@Test
public void getOrCreateLaunchRootUsesActivityOptionsWindowingMode() {
- final Task rootTask = createTaskStackOnDisplay(
- WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task rootTask = createTask(
+ mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
rootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
taskDisplayArea.setLaunchRootTask(
rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD});
- final Task candidateRootTask = createTaskStackOnDisplay(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task candidateRootTask = createTask(
+ mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -134,9 +118,8 @@
@Test
public void testActivityWithZBoost_taskDisplayAreaDoesNotMoveUp() {
- final Task stack = createTaskStackOnDisplay(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
task.addChild(activity, 0 /* addPos */);
final TaskDisplayArea taskDisplayArea = activity.getDisplayArea();
@@ -152,87 +135,103 @@
}
@Test
- public void testStackPositionChildAt() {
- // Test that always-on-top stack can't be moved to position other than top.
- final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
- final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
+ public void testRootTaskPositionChildAt() {
+ Task pinnedTask = createTask(
+ mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ // Root task should contain visible app window to be considered visible.
+ assertFalse(pinnedTask.isVisible());
+ final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent);
+ pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+ assertTrue(pinnedTask.isVisible());
- final WindowContainer taskStackContainer = stack1.getParent();
+ // Test that always-on-top root task can't be moved to position other than top.
+ final Task rootTask1 = createTask(mDisplayContent);
+ final Task rootTask2 = createTask(mDisplayContent);
- final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
- final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
- final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask);
- assertThat(pinnedStackPos).isGreaterThan(stack2Pos);
- assertThat(stack2Pos).isGreaterThan(stack1Pos);
+ final WindowContainer taskContainer = rootTask1.getParent();
- taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedTask, false);
- assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
- assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
+ final int rootTask1Pos = taskContainer.mChildren.indexOf(rootTask1);
+ final int rootTask2Pos = taskContainer.mChildren.indexOf(rootTask2);
+ final int pinnedTaskPos = taskContainer.mChildren.indexOf(pinnedTask);
+ assertThat(pinnedTaskPos).isGreaterThan(rootTask2Pos);
+ assertThat(rootTask2Pos).isGreaterThan(rootTask1Pos);
- taskStackContainer.positionChildAt(1, mPinnedTask, false);
- assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
- assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
+ taskContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedTask, false);
+ assertEquals(taskContainer.mChildren.get(rootTask1Pos), rootTask1);
+ assertEquals(taskContainer.mChildren.get(rootTask2Pos), rootTask2);
+ assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask);
+
+ taskContainer.positionChildAt(1, pinnedTask, false);
+ assertEquals(taskContainer.mChildren.get(rootTask1Pos), rootTask1);
+ assertEquals(taskContainer.mChildren.get(rootTask2Pos), rootTask2);
+ assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask);
}
@Test
- public void testStackPositionBelowPinnedStack() {
- // Test that no stack can be above pinned stack.
- final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
+ public void testRootTaskPositionBelowPinnedRootTask() {
+ Task pinnedTask = createTask(
+ mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ // Root task should contain visible app window to be considered visible.
+ assertFalse(pinnedTask.isVisible());
+ final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent);
+ pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+ assertTrue(pinnedTask.isVisible());
- final WindowContainer taskStackContainer = stack1.getParent();
+ // Test that no root task can be above pinned root task.
+ final Task rootTask1 = createTask(mDisplayContent);
- final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
- final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask);
- assertThat(pinnedStackPos).isGreaterThan(stackPos);
+ final WindowContainer taskContainer = rootTask1.getParent();
- taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
- assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
+ final int rootTaskPos = taskContainer.mChildren.indexOf(rootTask1);
+ final int pinnedTaskPos = taskContainer.mChildren.indexOf(pinnedTask);
+ assertThat(pinnedTaskPos).isGreaterThan(rootTaskPos);
- taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
- assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
+ taskContainer.positionChildAt(WindowContainer.POSITION_TOP, rootTask1, false);
+ assertEquals(taskContainer.mChildren.get(rootTaskPos), rootTask1);
+ assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask);
+
+ taskContainer.positionChildAt(taskContainer.mChildren.size() - 1, rootTask1, false);
+ assertEquals(taskContainer.mChildren.get(rootTaskPos), rootTask1);
+ assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask);
}
@Test
- public void testDisplayPositionWithPinnedStack() {
- // Make sure the display is trusted display which capable to move the stack to top.
+ public void testDisplayPositionWithPinnedRootTask() {
+ // Make sure the display is trusted display which capable to move the root task to top.
spyOn(mDisplayContent);
doReturn(true).when(mDisplayContent).isTrusted();
- // Allow child stack to move to top.
+ // Allow child root task to move to top.
mDisplayContent.mDontMoveToTop = false;
- // The display contains pinned stack that was added in {@link #setUp}.
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
+ // The display contains pinned root task that was added in {@link #setUp}.
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
// Add another display at top.
mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(),
false /* includingParents */);
// Move the task of {@code mDisplayContent} to top.
- stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
- final int indexOfDisplayWithPinnedStack = mWm.mRoot.mChildren.indexOf(mDisplayContent);
+ rootTask.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
+ final int indexOfDisplayWithPinnedRootTask = mWm.mRoot.mChildren.indexOf(mDisplayContent);
assertEquals("The testing DisplayContent should be moved to top with task",
- mWm.mRoot.getChildCount() - 1, indexOfDisplayWithPinnedStack);
+ mWm.mRoot.getChildCount() - 1, indexOfDisplayWithPinnedRootTask);
}
@Test
public void testMovingChildTaskOnTop() {
- // Make sure the display is trusted display which capable to move the stack to top.
+ // Make sure the display is trusted display which capable to move the root task to top.
spyOn(mDisplayContent);
doReturn(true).when(mDisplayContent).isTrusted();
- // Allow child stack to move to top.
+ // Allow child root task to move to top.
mDisplayContent.mDontMoveToTop = false;
- // The display contains pinned stack that was added in {@link #setUp}.
- Task stack = createTaskStackOnDisplay(mDisplayContent);
- Task task = createTaskInStack(stack, 0 /* userId */);
+ // The display contains pinned root task that was added in {@link #setUp}.
+ Task rootTask = createTask(mDisplayContent);
+ Task task = createTaskInRootTask(rootTask, 0 /* userId */);
// Add another display at top.
mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(),
@@ -243,7 +242,7 @@
mWm.mRoot.getChildCount() - 2, mWm.mRoot.mChildren.indexOf(mDisplayContent));
// Move the task of {@code mDisplayContent} to top.
- stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
+ rootTask.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
// Ensure that original display ({@code mDisplayContent}) is now on the top.
assertEquals("The testing DisplayContent should be moved to top with task",
@@ -252,16 +251,16 @@
@Test
public void testDontMovingChildTaskOnTop() {
- // Make sure the display is trusted display which capable to move the stack to top.
+ // Make sure the display is trusted display which capable to move the root task to top.
spyOn(mDisplayContent);
doReturn(true).when(mDisplayContent).isTrusted();
- // Allow child stack to move to top.
+ // Allow child root task to move to top.
mDisplayContent.mDontMoveToTop = true;
- // The display contains pinned stack that was added in {@link #setUp}.
- Task stack = createTaskStackOnDisplay(mDisplayContent);
- Task task = createTaskInStack(stack, 0 /* userId */);
+ // The display contains pinned root task that was added in {@link #setUp}.
+ Task rootTask = createTask(mDisplayContent);
+ Task task = createTaskInRootTask(rootTask, 0 /* userId */);
// Add another display at top.
mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(),
@@ -272,7 +271,7 @@
mWm.mRoot.getChildCount() - 2, mWm.mRoot.mChildren.indexOf(mDisplayContent));
// Try moving the task of {@code mDisplayContent} to top.
- stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
+ rootTask.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
// Ensure that original display ({@code mDisplayContent}) hasn't moved and is not
// on the top.
@@ -282,8 +281,7 @@
@Test
public void testReuseTaskAsRootTask() {
- final Task candidateTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task candidateTask = createTask(mDisplayContent);
final int type = ACTIVITY_TYPE_STANDARD;
assertGetOrCreateRootTask(WINDOWING_MODE_FULLSCREEN, type, candidateTask,
true /* reuseCandidate */);
@@ -312,7 +310,7 @@
}
@Test
- public void testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange() {
+ public void testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange() {
final RootWindowContainer rootWindowContainer = mWm.mAtmService.mRootWindowContainer;
final TaskDisplayArea defaultTaskDisplayArea =
rootWindowContainer.getDefaultTaskDisplayArea();
@@ -330,7 +328,7 @@
ActivityRecord primarySplitActivity = primarySplitTask.getTopNonFinishingActivity();
assertNotNull(primarySplitActivity);
primarySplitActivity.setState(RESUMED,
- "testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange");
+ "testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange");
ActivityRecord homeActivity = rootHomeTask.getTopNonFinishingActivity();
if (homeActivity == null) {
@@ -350,14 +348,14 @@
final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
FEATURE_VENDOR_FIRST);
- final Task firstStack = firstTaskDisplayArea.createRootTask(
+ final Task firstRootTask = firstTaskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final Task secondStack = secondTaskDisplayArea.createRootTask(
+ final Task secondRootTask = secondTaskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(firstStack).build();
+ .setTask(firstRootTask).build();
final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
- .setTask(secondStack).build();
+ .setTask(secondRootTask).build();
// Activity on TDA1 is focused
mDisplayContent.setFocusedApp(firstActivity);
@@ -384,14 +382,14 @@
final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
FEATURE_VENDOR_FIRST);
- final Task firstStack = firstTaskDisplayArea.createRootTask(
+ final Task firstRootTask = firstTaskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final Task secondStack = secondTaskDisplayArea.createRootTask(
+ final Task secondRootTask = secondTaskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(firstStack).build();
+ .setTask(firstRootTask).build();
final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
- .setTask(secondStack).build();
+ .setTask(secondRootTask).build();
firstTaskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
secondTaskDisplayArea.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
@@ -411,9 +409,9 @@
@Test
public void testIgnoreOrientationRequest() {
final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
- final Task stack = taskDisplayArea.createRootTask(
+ final Task task = taskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
mDisplayContent.setFocusedApp(activity);
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
@@ -428,7 +426,7 @@
@Test
@UseTestDisplay
public void testRemove_reparentToDefault() {
- final Task task = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTask(mDisplayContent);
final TaskDisplayArea displayArea = task.getDisplayArea();
displayArea.remove();
assertTrue(displayArea.isRemoved());
@@ -442,8 +440,8 @@
@Test
@UseTestDisplay
- public void testRemove_stackCreatedByOrganizer() {
- final Task task = createTaskStackOnDisplay(mDisplayContent);
+ public void testRemove_rootTaskCreatedByOrganizer() {
+ final Task task = createTask(mDisplayContent);
task.mCreatedByOrganizer = true;
final TaskDisplayArea displayArea = task.getDisplayArea();
displayArea.remove();
@@ -464,4 +462,221 @@
null /* activityOptions */);
assertEquals(reuseCandidate, rootTask == candidateTask);
}
+
+ @Test
+ public void testGetOrCreateRootHomeTask_defaultDisplay() {
+ TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea();
+
+ // Remove the current home root task if it exists so a new one can be created below.
+ Task homeTask = defaultTaskDisplayArea.getRootHomeTask();
+ if (homeTask != null) {
+ defaultTaskDisplayArea.removeChild(homeTask);
+ }
+ assertNull(defaultTaskDisplayArea.getRootHomeTask());
+
+ assertNotNull(defaultTaskDisplayArea.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
+ DisplayContent display = createNewDisplay();
+ doReturn(true).when(display).supportsSystemDecorations();
+
+ // Remove the current home root task if it exists so a new one can be created below.
+ TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+ Task homeTask = taskDisplayArea.getRootHomeTask();
+ if (homeTask != null) {
+ taskDisplayArea.removeChild(homeTask);
+ }
+ assertNull(taskDisplayArea.getRootHomeTask());
+
+ assertNotNull(taskDisplayArea.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
+ DisplayContent display = createNewDisplay();
+ TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+ doReturn(false).when(display).supportsSystemDecorations();
+
+ assertNull(taskDisplayArea.getRootHomeTask());
+ assertNull(taskDisplayArea.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testGetOrCreateRootHomeTask_untrustedDisplay() {
+ DisplayContent display = createNewDisplay();
+ TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+ doReturn(false).when(display).isTrusted();
+
+ assertNull(taskDisplayArea.getRootHomeTask());
+ assertNull(taskDisplayArea.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testGetOrCreateRootHomeTask_dontMoveToTop() {
+ DisplayContent display = createNewDisplay();
+ display.mDontMoveToTop = true;
+ TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+
+ assertNull(taskDisplayArea.getRootHomeTask());
+ assertNull(taskDisplayArea.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testLastFocusedRootTaskIsUpdatedWhenMovingRootTask() {
+ // Create a root task at bottom.
+ final TaskDisplayArea taskDisplayAreas =
+ mRootWindowContainer.getDefaultDisplay().getDefaultTaskDisplayArea();
+ final Task rootTask =
+ new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build();
+ final Task prevFocusedRootTask = taskDisplayAreas.getFocusedRootTask();
+
+ rootTask.moveToFront("moveRootTaskToFront");
+ // After moving the root task to front, the previous focused should be the last focused.
+ assertTrue(rootTask.isFocusedRootTaskOnDisplay());
+ assertEquals(prevFocusedRootTask, taskDisplayAreas.getLastFocusedRootTask());
+
+ rootTask.moveToBack("moveRootTaskToBack", null /* task */);
+ // After moving the root task to back, the root task should be the last focused.
+ assertEquals(rootTask, taskDisplayAreas.getLastFocusedRootTask());
+ }
+
+ /**
+ * This test simulates the picture-in-picture menu activity launches an activity to fullscreen
+ * root task. The fullscreen root task should be the top focused for resuming correctly.
+ */
+ @Test
+ public void testFullscreenRootTaskCanBeFocusedWhenFocusablePinnedRootTaskExists() {
+ // Create a pinned root task and move to front.
+ final Task pinnedRootTask = mRootWindowContainer.getDefaultTaskDisplayArea()
+ .createRootTask(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
+ final Task pinnedTask = new TaskBuilder(mAtm.mTaskSupervisor)
+ .setParentTask(pinnedRootTask).build();
+ new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
+ .setTask(pinnedTask).build();
+ pinnedRootTask.moveToFront("movePinnedRootTaskToFront");
+
+ // The focused root task should be the pinned root task.
+ assertTrue(pinnedRootTask.isFocusedRootTaskOnDisplay());
+
+ // Create a fullscreen root task and move to front.
+ final Task fullscreenRootTask = createTaskWithActivity(
+ mRootWindowContainer.getDefaultTaskDisplayArea(),
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true);
+ fullscreenRootTask.moveToFront("moveFullscreenRootTaskToFront");
+
+ // The focused root task should be the fullscreen root task.
+ assertTrue(fullscreenRootTask.isFocusedRootTaskOnDisplay());
+ }
+
+ /**
+ * Test {@link TaskDisplayArea#mPreferredTopFocusableRootTask} will be cleared when
+ * the root task is removed or moved to back, and the focused root task will be according to
+ * z-order.
+ */
+ @Test
+ public void testRootTaskShouldNotBeFocusedAfterMovingToBackOrRemoving() {
+ // Create a display which only contains 2 root task.
+ final DisplayContent display = addNewDisplayContentAt(POSITION_TOP);
+ final Task rootTask1 = createTaskWithActivity(display.getDefaultTaskDisplayArea(),
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true /* twoLevelTask */);
+ final Task rootTask2 = createTaskWithActivity(display.getDefaultTaskDisplayArea(),
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true /* twoLevelTask */);
+
+ // Put rootTask1 and rootTask2 on top.
+ rootTask1.moveToFront("moveRootTask1ToFront");
+ rootTask2.moveToFront("moveRootTask2ToFront");
+ assertTrue(rootTask2.isFocusedRootTaskOnDisplay());
+
+ // rootTask1 should be focused after moving rootTask2 to back.
+ rootTask2.moveToBack("moveRootTask2ToBack", null /* task */);
+ assertTrue(rootTask1.isFocusedRootTaskOnDisplay());
+
+ // rootTask2 should be focused after removing rootTask1.
+ rootTask1.getDisplayArea().removeRootTask(rootTask1);
+ assertTrue(rootTask2.isFocusedRootTaskOnDisplay());
+ }
+
+ /**
+ * This test enforces that alwaysOnTop root task is placed at proper position.
+ */
+ @Test
+ public void testAlwaysOnTopRootTaskLocation() {
+ final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
+ final Task alwaysOnTopRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM,
+ ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(alwaysOnTopRootTask).build();
+ alwaysOnTopRootTask.setAlwaysOnTop(true);
+ taskDisplayArea.positionChildAt(POSITION_TOP, alwaysOnTopRootTask,
+ false /* includingParents */);
+ assertTrue(alwaysOnTopRootTask.isAlwaysOnTop());
+ // Ensure always on top state is synced to the children of the root task.
+ assertTrue(alwaysOnTopRootTask.getTopNonFinishingActivity().isAlwaysOnTop());
+ assertEquals(alwaysOnTopRootTask, taskDisplayArea.getTopRootTask());
+
+ final Task pinnedRootTask = taskDisplayArea.createRootTask(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ assertEquals(pinnedRootTask, taskDisplayArea.getRootPinnedTask());
+ assertEquals(pinnedRootTask, taskDisplayArea.getTopRootTask());
+
+ final Task anotherAlwaysOnTopRootTask = taskDisplayArea.createRootTask(
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ anotherAlwaysOnTopRootTask.setAlwaysOnTop(true);
+ taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopRootTask,
+ false /* includingParents */);
+ assertTrue(anotherAlwaysOnTopRootTask.isAlwaysOnTop());
+ int topPosition = taskDisplayArea.getRootTaskCount() - 1;
+ // Ensure the new alwaysOnTop root task is put below the pinned root task, but on top of the
+ // existing alwaysOnTop root task.
+ assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask));
+
+ final Task nonAlwaysOnTopRootTask = taskDisplayArea.createRootTask(
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ assertEquals(taskDisplayArea, nonAlwaysOnTopRootTask.getDisplayArea());
+ topPosition = taskDisplayArea.getRootTaskCount() - 1;
+ // Ensure the non-alwaysOnTop root task is put below the three alwaysOnTop root tasks, but
+ // above the existing other non-alwaysOnTop root tasks.
+ assertEquals(topPosition - 3, getTaskIndexOf(taskDisplayArea, nonAlwaysOnTopRootTask));
+
+ anotherAlwaysOnTopRootTask.setAlwaysOnTop(false);
+ taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopRootTask,
+ false /* includingParents */);
+ assertFalse(anotherAlwaysOnTopRootTask.isAlwaysOnTop());
+ // Ensure, when always on top is turned off for a root task, the root task is put just below
+ // all other always on top root tasks.
+ assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask));
+ anotherAlwaysOnTopRootTask.setAlwaysOnTop(true);
+
+ // Ensure always on top state changes properly when windowing mode changes.
+ anotherAlwaysOnTopRootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertFalse(anotherAlwaysOnTopRootTask.isAlwaysOnTop());
+ assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask));
+ anotherAlwaysOnTopRootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertTrue(anotherAlwaysOnTopRootTask.isAlwaysOnTop());
+ assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask));
+
+ final Task dreamRootTask = taskDisplayArea.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, true /* onTop */);
+ assertEquals(taskDisplayArea, dreamRootTask.getDisplayArea());
+ assertTrue(dreamRootTask.isAlwaysOnTop());
+ topPosition = taskDisplayArea.getRootTaskCount() - 1;
+ // Ensure dream shows above all activities, including PiP
+ assertEquals(dreamRootTask, taskDisplayArea.getTopRootTask());
+ assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, pinnedRootTask));
+
+ final Task assistRootTask = taskDisplayArea.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+ assertEquals(taskDisplayArea, assistRootTask.getDisplayArea());
+ assertFalse(assistRootTask.isAlwaysOnTop());
+ topPosition = taskDisplayArea.getRootTaskCount() - 1;
+
+ // Ensure Assistant shows as a non-always-on-top activity when config_assistantOnTopOfDream
+ // is false and on top of everything when true.
+ final boolean isAssistantOnTop = mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_assistantOnTopOfDream);
+ assertEquals(isAssistantOnTop ? topPosition : topPosition - 4,
+ getTaskIndexOf(taskDisplayArea, assistRootTask));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index ed57294..de4c40d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -403,8 +403,8 @@
public void testOverridesDisplayAreaWithStandardTypeAndFullscreenMode() {
final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay,
mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
- final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, secondaryDisplayArea);
+ final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
launchRoot.mCreatedByOrganizer = true;
secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FULLSCREEN },
@@ -419,8 +419,8 @@
public void testOverridesDisplayAreaWithHomeTypeAndFullscreenMode() {
final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay,
mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
- final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, secondaryDisplayArea);
+ final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
launchRoot.mCreatedByOrganizer = true;
mActivity.setActivityType(ACTIVITY_TYPE_HOME);
@@ -438,8 +438,8 @@
WINDOWING_MODE_FREEFORM);
final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(freeformDisplay,
mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
- final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, secondaryDisplayArea);
+ final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
launchRoot.mCreatedByOrganizer = true;
secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM },
@@ -455,8 +455,8 @@
public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayArea() {
final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay,
mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
- final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, secondaryDisplayArea);
+ final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
launchRoot.mCreatedByOrganizer = true;
secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FULLSCREEN },
@@ -481,8 +481,8 @@
mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0,
DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height());
- final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, secondaryDisplayArea);
+ final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
launchRoot.mCreatedByOrganizer = true;
secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM },
new int[] { ACTIVITY_TYPE_STANDARD });
@@ -512,8 +512,8 @@
mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0,
DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height());
- final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, secondaryDisplayArea);
+ final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
launchRoot.mCreatedByOrganizer = true;
secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM },
new int[] { ACTIVITY_TYPE_STANDARD });
@@ -1687,16 +1687,16 @@
}
private ActivityRecord createSourceActivity(TestDisplayContent display) {
- final Task stack = display.getDefaultTaskDisplayArea()
+ final Task rootTask = display.getDefaultTaskDisplayArea()
.createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
- return new ActivityBuilder(mAtm).setTask(stack).build();
+ return new ActivityBuilder(mAtm).setTask(rootTask).build();
}
private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) {
- final Task stack = display.getDefaultTaskDisplayArea()
+ final Task rootTask = display.getDefaultTaskDisplayArea()
.createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
- stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
- final Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build();
+ rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
// Just work around the unnecessary adjustments for bounds.
task.getWindowConfiguration().setBounds(bounds);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
deleted file mode 100644
index d853b93..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ /dev/null
@@ -1,1161 +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 static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
-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_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.util.DisplayMetrics.DENSITY_DEFAULT;
-import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_90;
-import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.same;
-
-import android.app.ActivityManager;
-import android.app.TaskInfo;
-import android.app.WindowConfiguration;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.util.DisplayMetrics;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
-import android.util.Xml;
-import android.view.DisplayInfo;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-
-/**
- * Tests for exercising {@link Task}.
- *
- * Build/Install/Run:
- * atest WmTests:TaskRecordTests
- */
-@MediumTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class TaskRecordTests extends WindowTestsBase {
-
- private static final String TASK_TAG = "task";
-
- private Rect mParentBounds;
-
- @Before
- public void setUp() throws Exception {
- mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
- removeGlobalMinSizeRestriction();
- }
-
- @Test
- public void testRestoreWindowedTask() throws Exception {
- final Task expected = createTask(64);
- expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100);
-
- final byte[] serializedBytes = serializeToBytes(expected);
- final Task actual = restoreFromBytes(serializedBytes);
- assertEquals(expected.mTaskId, actual.mTaskId);
- assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds);
- }
-
- /** Ensure we have no chance to modify the original intent. */
- @Test
- public void testCopyBaseIntentForTaskInfo() {
- final Task task = createTask(1);
- task.setTaskDescription(new ActivityManager.TaskDescription());
- final TaskInfo info = task.getTaskInfo();
-
- // The intent of info should be a copy so assert that they are different instances.
- assertThat(info.baseIntent, not(sameInstance(task.getBaseIntent())));
- }
-
- @Test
- public void testReturnsToHomeStack() throws Exception {
- final Task task = createTask(1);
- spyOn(task);
- doReturn(true).when(task).hasChild();
- assertFalse(task.returnsToHomeRootTask());
- task.intent = null;
- assertFalse(task.returnsToHomeRootTask());
- task.intent = new Intent();
- assertFalse(task.returnsToHomeRootTask());
- task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
- assertTrue(task.returnsToHomeRootTask());
- }
-
- /** Ensures that empty bounds cause appBounds to inherit from parent. */
- @Test
- public void testAppBounds_EmptyBounds() {
- final Rect emptyBounds = new Rect();
- testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
- mParentBounds);
- }
-
- /** Ensures that bounds on freeform stacks are not clipped. */
- @Test
- public void testAppBounds_FreeFormBounds() {
- final Rect freeFormBounds = new Rect(mParentBounds);
- freeFormBounds.offset(10, 10);
- testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
- freeFormBounds);
- }
-
- /** Ensures that fully contained bounds are not clipped. */
- @Test
- public void testAppBounds_ContainedBounds() {
- final Rect insetBounds = new Rect(mParentBounds);
- insetBounds.inset(5, 5, 5, 5);
- testStackBoundsConfiguration(
- WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds);
- }
-
- @Test
- public void testFitWithinBounds() {
- final Rect parentBounds = new Rect(10, 10, 200, 200);
- TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
- Task stack = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM,
- ACTIVITY_TYPE_STANDARD, true /* onTop */);
- Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build();
- final Configuration parentConfig = stack.getConfiguration();
- parentConfig.windowConfiguration.setBounds(parentBounds);
- parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
-
- // check top and left
- Rect reqBounds = new Rect(-190, -190, 0, 0);
- task.setBounds(reqBounds);
- // Make sure part of it is exposed
- assertTrue(task.getBounds().right > parentBounds.left);
- assertTrue(task.getBounds().bottom > parentBounds.top);
- // Should still be more-or-less in that corner
- assertTrue(task.getBounds().left <= parentBounds.left);
- assertTrue(task.getBounds().top <= parentBounds.top);
-
- assertEquals(reqBounds.width(), task.getBounds().width());
- assertEquals(reqBounds.height(), task.getBounds().height());
-
- // check bottom and right
- reqBounds = new Rect(210, 210, 400, 400);
- task.setBounds(reqBounds);
- // Make sure part of it is exposed
- assertTrue(task.getBounds().left < parentBounds.right);
- assertTrue(task.getBounds().top < parentBounds.bottom);
- // Should still be more-or-less in that corner
- assertTrue(task.getBounds().right >= parentBounds.right);
- assertTrue(task.getBounds().bottom >= parentBounds.bottom);
-
- assertEquals(reqBounds.width(), task.getBounds().width());
- assertEquals(reqBounds.height(), task.getBounds().height());
- }
-
- /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
- @Test
- public void testBoundsOnModeChangeFreeformToFullscreen() {
- DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
- Task stack = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true)
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- Task task = stack.getBottomMostTask();
- task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
- DisplayInfo info = new DisplayInfo();
- display.mDisplay.getDisplayInfo(info);
- final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
- final Rect freeformBounds = new Rect(fullScreenBounds);
- freeformBounds.inset((int) (freeformBounds.width() * 0.2),
- (int) (freeformBounds.height() * 0.2));
- task.setBounds(freeformBounds);
-
- assertEquals(freeformBounds, task.getBounds());
-
- // FULLSCREEN inherits bounds
- stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertEquals(fullScreenBounds, task.getBounds());
- assertEquals(freeformBounds, task.mLastNonFullscreenBounds);
-
- // FREEFORM restores bounds
- stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertEquals(freeformBounds, task.getBounds());
- }
-
- /**
- * Tests that a task with forced orientation has orientation-consistent bounds within the
- * parent.
- */
- @Test
- public void testFullscreenBoundsForcedOrientation() {
- final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
- final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
- final DisplayContent display = new TestDisplayContent.Builder(mAtm,
- fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build();
- assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId));
- // Fix the display orientation to landscape which is the natural rotation (0) for the test
- // display.
- final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
- dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
- dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
-
- final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
- final Task task = stack.getBottomMostTask();
- final ActivityRecord root = task.getTopNonFinishingActivity();
-
- assertEquals(fullScreenBounds, task.getBounds());
-
- // Setting app to fixed portrait fits within parent
- root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(root, task.getRootActivity());
- assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
- // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds.
- assertThat(task.getBounds().height()).isLessThan(task.getBounds().width());
- assertEquals(fullScreenBounds, task.getBounds());
-
- // Top activity gets used
- final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(stack)
- .build();
- assertEquals(top, task.getTopNonFinishingActivity());
- top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
- assertEquals(task.getBounds().width(), fullScreenBounds.width());
-
- // Setting app to unspecified restores
- top.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
- assertEquals(fullScreenBounds, task.getBounds());
-
- // Setting app to fixed landscape and changing display
- top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- // Fix the display orientation to portrait which is 90 degrees for the test display.
- dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90);
-
- // Fixed orientation request should be resolved on activity level. Task fills display
- // bounds.
- assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width());
- assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height());
- assertEquals(fullScreenBoundsPort, task.getBounds());
-
- // in FREEFORM, no constraint
- final Rect freeformBounds = new Rect(display.getBounds());
- freeformBounds.inset((int) (freeformBounds.width() * 0.2),
- (int) (freeformBounds.height() * 0.2));
- stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
- task.setBounds(freeformBounds);
- assertEquals(freeformBounds, task.getBounds());
-
- // FULLSCREEN letterboxes bounds on activity level, no constraint on task level.
- stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width());
- assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height());
- assertEquals(fullScreenBoundsPort, task.getBounds());
-
- // FREEFORM restores bounds as before
- stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertEquals(freeformBounds, task.getBounds());
- }
-
- @Test
- public void testReportsOrientationRequestInLetterboxForOrientation() {
- final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
- final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
- final DisplayContent display = new TestDisplayContent.Builder(mAtm,
- fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build();
- assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId));
- // Fix the display orientation to landscape which is the natural rotation (0) for the test
- // display.
- final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
- dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
- dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
-
- final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
- final Task task = stack.getBottomMostTask();
- ActivityRecord root = task.getTopNonFinishingActivity();
-
- assertEquals(fullScreenBounds, task.getBounds());
-
- // Setting app to fixed portrait fits within parent on activity level. Task fills parent.
- root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertThat(root.getBounds().width()).isLessThan(root.getBounds().height());
- assertEquals(task.getBounds(), fullScreenBounds);
-
- assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation());
- }
-
- @Test
- public void testIgnoresForcedOrientationWhenParentHandles() {
- final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
- DisplayContent display = new TestDisplayContent.Builder(
- mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
-
- display.getRequestedOverrideConfiguration().orientation =
- Configuration.ORIENTATION_LANDSCAPE;
- display.onRequestedOverrideConfigurationChanged(
- display.getRequestedOverrideConfiguration());
- Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
- Task task = stack.getBottomMostTask();
- ActivityRecord root = task.getTopNonFinishingActivity();
-
- final WindowContainer parentWindowContainer =
- new WindowContainer(mSystemServicesTestRule.getWindowManagerService());
- spyOn(parentWindowContainer);
- parentWindowContainer.setBounds(fullScreenBounds);
- doReturn(parentWindowContainer).when(task).getParent();
- doReturn(display.getDefaultTaskDisplayArea()).when(task).getDisplayArea();
- doReturn(stack).when(task).getRootTask();
- doReturn(true).when(parentWindowContainer).handlesOrientationChangeFromDescendant();
-
- // Setting app to fixed portrait fits within parent, but Task shouldn't adjust the
- // bounds because its parent says it will handle it at a later time.
- root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(root, task.getRootActivity());
- assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
- assertEquals(fullScreenBounds, task.getBounds());
- }
-
- @Test
- public void testComputeConfigResourceOverrides() {
- final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920);
- TestDisplayContent display = new TestDisplayContent.Builder(
- mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
- final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
- final Configuration inOutConfig = new Configuration();
- final Configuration parentConfig = new Configuration();
- final int longSide = 1200;
- final int shortSide = 600;
- final Rect parentBounds = new Rect(0, 0, 250, 500);
- final Rect parentAppBounds = new Rect(0, 0, 250, 480);
- parentConfig.windowConfiguration.setBounds(parentBounds);
- parentConfig.windowConfiguration.setAppBounds(parentAppBounds);
- parentConfig.densityDpi = 400;
- parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200
- parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100
- parentConfig.windowConfiguration.setRotation(ROTATION_0);
-
- // By default, the input bounds will fill parent.
- task.computeConfigResourceOverrides(inOutConfig, parentConfig);
-
- assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
- assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
- assertEquals(parentAppBounds, inOutConfig.windowConfiguration.getAppBounds());
- assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation);
-
- // If bounds are overridden, config properties should be made to match. Surface hierarchy
- // will crop for policy.
- inOutConfig.setToDefaults();
- final Rect largerPortraitBounds = new Rect(0, 0, shortSide, longSide);
- inOutConfig.windowConfiguration.setBounds(largerPortraitBounds);
- task.computeConfigResourceOverrides(inOutConfig, parentConfig);
- // The override bounds are beyond the parent, the out appBounds should not be intersected
- // by parent appBounds.
- assertEquals(largerPortraitBounds, inOutConfig.windowConfiguration.getAppBounds());
- assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160);
- assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160);
-
- inOutConfig.setToDefaults();
- // Landscape bounds.
- final Rect largerLandscapeBounds = new Rect(0, 0, longSide, shortSide);
- inOutConfig.windowConfiguration.setBounds(largerLandscapeBounds);
-
- // Setup the display with a top stable inset. The later assertion will ensure the inset is
- // excluded from screenHeightDp.
- final int statusBarHeight = 100;
- final DisplayPolicy policy = display.getDisplayPolicy();
- doAnswer(invocationOnMock -> {
- final Rect insets = invocationOnMock.<Rect>getArgument(0);
- insets.top = statusBarHeight;
- return null;
- }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
-
- // Without limiting to be inside the parent bounds, the out screen size should keep relative
- // to the input bounds.
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
- final ActivityRecord.CompatDisplayInsets compatIntsets =
- new ActivityRecord.CompatDisplayInsets(
- display, activity, /* fixedOrientationBounds= */ null);
- task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets);
-
- assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
- assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi,
- inOutConfig.screenHeightDp);
- assertEquals(longSide * DENSITY_DEFAULT / parentConfig.densityDpi,
- inOutConfig.screenWidthDp);
- assertEquals(Configuration.ORIENTATION_LANDSCAPE, inOutConfig.orientation);
- }
-
- @Test
- public void testComputeConfigResourceLayoutOverrides() {
- final Rect fullScreenBounds = new Rect(0, 0, 1000, 2500);
- TestDisplayContent display = new TestDisplayContent.Builder(
- mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
- final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
- final Configuration inOutConfig = new Configuration();
- final Configuration parentConfig = new Configuration();
- final Rect nonLongBounds = new Rect(0, 0, 1000, 1250);
- parentConfig.windowConfiguration.setBounds(fullScreenBounds);
- parentConfig.windowConfiguration.setAppBounds(fullScreenBounds);
- parentConfig.densityDpi = 400;
- parentConfig.screenHeightDp = (fullScreenBounds.bottom * 160) / parentConfig.densityDpi;
- parentConfig.screenWidthDp = (fullScreenBounds.right * 160) / parentConfig.densityDpi;
- parentConfig.windowConfiguration.setRotation(ROTATION_0);
-
- // Set BOTH screenW/H to an override value
- inOutConfig.screenWidthDp = nonLongBounds.width() * 160 / parentConfig.densityDpi;
- inOutConfig.screenHeightDp = nonLongBounds.height() * 160 / parentConfig.densityDpi;
- task.computeConfigResourceOverrides(inOutConfig, parentConfig);
-
- // screenLayout should honor override when both screenW/H are set.
- assertTrue((inOutConfig.screenLayout & Configuration.SCREENLAYOUT_LONG_NO) != 0);
- }
-
- @Test
- public void testComputeNestedConfigResourceOverrides() {
- final Task task = new TaskBuilder(mSupervisor).build();
- assertTrue(task.getResolvedOverrideBounds().isEmpty());
- int origScreenH = task.getConfiguration().screenHeightDp;
- Configuration stackConfig = new Configuration();
- stackConfig.setTo(task.getRootTask().getRequestedOverrideConfiguration());
- stackConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-
- // Set bounds on stack (not task) and verify that the task resource configuration changes
- // despite it's override bounds being empty.
- Rect bounds = new Rect(task.getRootTask().getBounds());
- bounds.bottom = (int) (bounds.bottom * 0.6f);
- stackConfig.windowConfiguration.setBounds(bounds);
- task.getRootTask().onRequestedOverrideConfigurationChanged(stackConfig);
- assertNotEquals(origScreenH, task.getConfiguration().screenHeightDp);
- }
-
- @Test
- public void testFullScreenTaskNotAdjustedByMinimalSize() {
- final Task fullscreenTask = new TaskBuilder(mSupervisor).build();
- final Rect originalTaskBounds = new Rect(fullscreenTask.getBounds());
- final ActivityInfo aInfo = new ActivityInfo();
- aInfo.windowLayout = new ActivityInfo.WindowLayout(0 /* width */, 0 /* widthFraction */,
- 0 /* height */, 0 /* heightFraction */, 0 /* gravity */,
- originalTaskBounds.width() * 2 /* minWidth */,
- originalTaskBounds.height() * 2 /* minHeight */);
- fullscreenTask.setMinDimensions(aInfo);
- fullscreenTask.onConfigurationChanged(fullscreenTask.getParent().getConfiguration());
-
- assertEquals(originalTaskBounds, fullscreenTask.getBounds());
- }
-
- @Test
- public void testInsetDisregardedWhenFreeformOverlapsNavBar() {
- TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
- Task stack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, true /* onTop */);
- DisplayInfo displayInfo = new DisplayInfo();
- mAtm.mContext.getDisplay().getDisplayInfo(displayInfo);
- final int displayHeight = displayInfo.logicalHeight;
- final Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build();
- final Configuration inOutConfig = new Configuration();
- final Configuration parentConfig = new Configuration();
- final int longSide = 1200;
- final int shortSide = 600;
- parentConfig.densityDpi = 400;
- parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px
- parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
- parentConfig.windowConfiguration.setRotation(ROTATION_0);
-
- final int longSideDp = 480; // longSide / density = 1200 / 400 * 160
- final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160
- final int screenLayout = parentConfig.screenLayout
- & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
- final int reducedScreenLayout =
- Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp);
-
- // Portrait bounds overlapping with navigation bar, without insets.
- final Rect freeformBounds = new Rect(0,
- displayHeight - 10 - longSide,
- shortSide,
- displayHeight - 10);
- inOutConfig.windowConfiguration.setBounds(freeformBounds);
- // Set to freeform mode to verify bug fix.
- inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-
- task.computeConfigResourceOverrides(inOutConfig, parentConfig);
-
- // screenW/H should not be effected by parent since overridden and freeform
- assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
- inOutConfig.screenWidthDp);
- assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
- inOutConfig.screenHeightDp);
- assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
-
- inOutConfig.setToDefaults();
- // Landscape bounds overlapping with navigtion bar, without insets.
- freeformBounds.set(0,
- displayHeight - 10 - shortSide,
- longSide,
- displayHeight - 10);
- inOutConfig.windowConfiguration.setBounds(freeformBounds);
- inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-
- task.computeConfigResourceOverrides(inOutConfig, parentConfig);
-
- assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
- inOutConfig.screenWidthDp);
- assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
- inOutConfig.screenHeightDp);
- assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
- }
-
- /** Ensures that the alias intent won't have target component resolved. */
- @Test
- public void testTaskIntentActivityAlias() {
- final String aliasClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".aliasActivity";
- final String targetClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".targetActivity";
- final ComponentName aliasComponent =
- new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasClassName);
- final ComponentName targetComponent =
- new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, targetClassName);
-
- final Intent intent = new Intent();
- intent.setComponent(aliasComponent);
- final ActivityInfo info = new ActivityInfo();
- info.applicationInfo = new ApplicationInfo();
- info.packageName = DEFAULT_COMPONENT_PACKAGE_NAME;
- info.targetActivity = targetClassName;
-
- final Task task = new Task.Builder(mAtm)
- .setTaskId(1)
- .setActivityInfo(info)
- .setIntent(intent)
- .build();
- assertEquals("The alias activity component should be saved in task intent.", aliasClassName,
- task.intent.getComponent().getClassName());
-
- ActivityRecord aliasActivity = new ActivityBuilder(mAtm).setComponent(
- aliasComponent).setTargetActivity(targetClassName).build();
- assertEquals("Should be the same intent filter.", true,
- task.isSameIntentFilter(aliasActivity));
-
- ActivityRecord targetActivity = new ActivityBuilder(mAtm).setComponent(
- targetComponent).build();
- assertEquals("Should be the same intent filter.", true,
- task.isSameIntentFilter(targetActivity));
-
- ActivityRecord defaultActivity = new ActivityBuilder(mAtm).build();
- assertEquals("Should not be the same intent filter.", false,
- task.isSameIntentFilter(defaultActivity));
- }
-
- /** Test that root activity index is reported correctly for several activities in the task. */
- @Test
- public void testFindRootIndex() {
- final Task task = getTestTask();
- // Add an extra activity on top of the root one
- new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals("The root activity in the task must be reported.", task.getChildAt(0),
- task.getRootActivity(
- true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
- }
-
- /**
- * Test that root activity index is reported correctly for several activities in the task when
- * the activities on the bottom are finishing.
- */
- @Test
- public void testFindRootIndex_finishing() {
- final Task task = getTestTask();
- // Add extra two activities and mark the two on the bottom as finishing.
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.finishing = true;
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- activity1.finishing = true;
- new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals("The first non-finishing activity in the task must be reported.",
- task.getChildAt(2), task.getRootActivity(
- true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
- }
-
- /**
- * Test that root activity index is reported correctly for several activities in the task when
- * looking for the 'effective root'.
- */
- @Test
- public void testFindRootIndex_effectiveRoot() {
- final Task task = getTestTask();
- // Add an extra activity on top of the root one
- new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals("The root activity in the task must be reported.",
- task.getChildAt(0), task.getRootActivity(
- false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
- }
-
- /**
- * Test that root activity index is reported correctly when looking for the 'effective root' in
- * case when bottom activities are relinquishing task identity or finishing.
- */
- @Test
- public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() {
- final Task task = getTestTask();
- // Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and
- // one above as finishing.
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- activity1.finishing = true;
- new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals("The first non-finishing activity and non-relinquishing task identity "
- + "must be reported.", task.getChildAt(2), task.getRootActivity(
- false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
- }
-
- /**
- * Test that root activity index is reported correctly when looking for the 'effective root'
- * for the case when there is only a single activity that also has relinquishTaskIdentity set.
- */
- @Test
- public void testFindRootIndex_effectiveRoot_relinquishingAndSingleActivity() {
- final Task task = getTestTask();
- // Set relinquishTaskIdentity for the only activity in the task
- task.getBottomMostActivity().info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
-
- assertEquals("The root activity in the task must be reported.",
- task.getChildAt(0), task.getRootActivity(
- false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
- }
-
- /**
- * Test that the topmost activity index is reported correctly when looking for the
- * 'effective root' for the case when all activities have relinquishTaskIdentity set.
- */
- @Test
- public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() {
- final Task task = getTestTask();
- // Set relinquishTaskIdentity for all activities in the task
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
-
- assertEquals("The topmost activity in the task must be reported.",
- task.getChildAt(task.getChildCount() - 1), task.getRootActivity(
- false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
- }
-
- /** Test that bottom-most activity is reported in {@link Task#getRootActivity()}. */
- @Test
- public void testGetRootActivity() {
- final Task task = getTestTask();
- // Add an extra activity on top of the root one
- new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals("The root activity in the task must be reported.",
- task.getBottomMostActivity(), task.getRootActivity());
- }
-
- /**
- * Test that first non-finishing activity is reported in {@link Task#getRootActivity()}.
- */
- @Test
- public void testGetRootActivity_finishing() {
- final Task task = getTestTask();
- // Add an extra activity on top of the root one
- new ActivityBuilder(mAtm).setTask(task).build();
- // Mark the root as finishing
- task.getBottomMostActivity().finishing = true;
-
- assertEquals("The first non-finishing activity in the task must be reported.",
- task.getChildAt(1), task.getRootActivity());
- }
-
- /**
- * Test that relinquishTaskIdentity flag is ignored in {@link Task#getRootActivity()}.
- */
- @Test
- public void testGetRootActivity_relinquishTaskIdentity() {
- final Task task = getTestTask();
- // Mark the bottom-most activity with FLAG_RELINQUISH_TASK_IDENTITY.
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
- // Add an extra activity on top of the root one.
- new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals("The root activity in the task must be reported.",
- task.getBottomMostActivity(), task.getRootActivity());
- }
-
- /**
- * Test that no activity is reported in {@link Task#getRootActivity()} when all activities
- * in the task are finishing.
- */
- @Test
- public void testGetRootActivity_allFinishing() {
- final Task task = getTestTask();
- // Mark the bottom-most activity as finishing.
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.finishing = true;
- // Add an extra activity on top of the root one and mark it as finishing
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- activity1.finishing = true;
-
- assertNull("No activity must be reported if all are finishing", task.getRootActivity());
- }
-
- /**
- * Test that first non-finishing activity is the root of task.
- */
- @Test
- public void testIsRootActivity() {
- final Task task = getTestTask();
- // Mark the bottom-most activity as finishing.
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.finishing = true;
- // Add an extra activity on top of the root one.
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
-
- assertFalse("Finishing activity must not be the root of task", activity0.isRootOfTask());
- assertTrue("Non-finishing activity must be the root of task", activity1.isRootOfTask());
- }
-
- /**
- * Test that if all activities in the task are finishing, then the one on the bottom is the
- * root of task.
- */
- @Test
- public void testIsRootActivity_allFinishing() {
- final Task task = getTestTask();
- // Mark the bottom-most activity as finishing.
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.finishing = true;
- // Add an extra activity on top of the root one and mark it as finishing
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- activity1.finishing = true;
-
- assertTrue("Bottom activity must be the root of task", activity0.isRootOfTask());
- assertFalse("Finishing activity on top must not be the root of task",
- activity1.isRootOfTask());
- }
-
- /**
- * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)}.
- */
- @Test
- public void testGetTaskForActivity() {
- final Task task0 = getTestTask();
- final ActivityRecord activity0 = task0.getBottomMostActivity();
-
- final Task task1 = getTestTask();
- final ActivityRecord activity1 = task1.getBottomMostActivity();
-
- assertEquals(task0.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
- assertEquals(task1.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */));
- }
-
- /**
- * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with finishing
- * activity.
- */
- @Test
- public void testGetTaskForActivity_onlyRoot_finishing() {
- final Task task = getTestTask();
- // Make the current root activity finishing
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.finishing = true;
- // Add an extra activity on top - this will be the new root
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- // Add one more on top
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
- assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */));
- assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
- ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */));
- }
-
- /**
- * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with activity that
- * relinquishes task identity.
- */
- @Test
- public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() {
- final Task task = getTestTask();
- // Make the current root activity relinquish task identity
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
- // Add an extra activity on top - this will be the new root
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- // Add one more on top
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
- assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */));
- assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
- ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */));
- }
-
- /**
- * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} allowing non-root
- * entries.
- */
- @Test
- public void testGetTaskForActivity_notOnlyRoot() {
- final Task task = getTestTask();
- // Mark the bottom-most activity as finishing.
- final ActivityRecord activity0 = task.getBottomMostActivity();
- activity0.finishing = true;
-
- // Add an extra activity on top of the root one and make it relinquish task identity
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
-
- // Add one more activity on top
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
-
- assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
- assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */));
- assertEquals(task.mTaskId,
- ActivityRecord.getTaskForActivityLocked(activity2.appToken, false /* onlyRoot */));
- }
-
- /**
- * Test {@link Task#updateEffectiveIntent()}.
- */
- @Test
- public void testUpdateEffectiveIntent() {
- // Test simple case with a single activity.
- final Task task = getTestTask();
- final ActivityRecord activity0 = task.getBottomMostActivity();
-
- spyOn(task);
- task.updateEffectiveIntent();
- verify(task).setIntent(eq(activity0));
- }
-
- /**
- * Test {@link Task#updateEffectiveIntent()} with root activity marked as finishing. This
- * should make the task use the second activity when updating the intent.
- */
- @Test
- public void testUpdateEffectiveIntent_rootFinishing() {
- // Test simple case with a single activity.
- final Task task = getTestTask();
- final ActivityRecord activity0 = task.getBottomMostActivity();
- // Mark the bottom-most activity as finishing.
- activity0.finishing = true;
- // Add an extra activity on top of the root one
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
-
- spyOn(task);
- task.updateEffectiveIntent();
- verify(task).setIntent(eq(activity1));
- }
-
- /**
- * Test {@link Task#updateEffectiveIntent()} when all activities are finishing or
- * relinquishing task identity. In this case the root activity should still be used when
- * updating the intent (legacy behavior).
- */
- @Test
- public void testUpdateEffectiveIntent_allFinishing() {
- // Test simple case with a single activity.
- final Task task = getTestTask();
- final ActivityRecord activity0 = task.getBottomMostActivity();
- // Mark the bottom-most activity as finishing.
- activity0.finishing = true;
- // Add an extra activity on top of the root one and make it relinquish task identity
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
- activity1.finishing = true;
-
- // Task must still update the intent using the root activity (preserving legacy behavior).
- spyOn(task);
- task.updateEffectiveIntent();
- verify(task).setIntent(eq(activity0));
- }
-
- @Test
- public void testSaveLaunchingStateWhenConfigurationChanged() {
- LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
- spyOn(persister);
-
- final Task task = getTestTask();
- task.setHasBeenVisible(false);
- task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
- task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
- task.setHasBeenVisible(true);
- task.onConfigurationChanged(task.getParent().getConfiguration());
-
- verify(persister).saveTask(task, task.getDisplayContent());
- }
-
- @Test
- public void testSaveLaunchingStateWhenClearingParent() {
- LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
- spyOn(persister);
-
- final Task task = getTestTask();
- task.setHasBeenVisible(false);
- task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
- task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final DisplayContent oldDisplay = task.getDisplayContent();
-
- LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams();
- params.mWindowingMode = WINDOWING_MODE_UNDEFINED;
- persister.getLaunchParams(task, null, params);
- assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode);
-
- task.setHasBeenVisible(true);
- task.removeImmediately();
-
- verify(persister).saveTask(task, oldDisplay);
-
- persister.getLaunchParams(task, null, params);
- assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode);
- }
-
- @Test
- public void testNotSaveLaunchingStateNonFreeformDisplay() {
- LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
- spyOn(persister);
-
- final Task task = getTestTask();
- task.setHasBeenVisible(false);
- task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
- task.setHasBeenVisible(true);
- task.onConfigurationChanged(task.getParent().getConfiguration());
-
- verify(persister, never()).saveTask(same(task), any());
- }
-
- @Test
- public void testNotSaveLaunchingStateWhenNotFullscreenOrFreeformWindow() {
- LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
- spyOn(persister);
-
- final Task task = getTestTask();
- task.setHasBeenVisible(false);
- task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
- task.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED);
-
- task.setHasBeenVisible(true);
- task.onConfigurationChanged(task.getParent().getConfiguration());
-
- verify(persister, never()).saveTask(same(task), any());
- }
-
- @Test
- public void testNotSaveLaunchingStateForNonLeafTask() {
- LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
- spyOn(persister);
-
- final Task task = getTestTask();
- task.setHasBeenVisible(false);
- task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
- task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
- final Task leafTask = createTaskInStack(task, 0 /* userId */);
-
- leafTask.setHasBeenVisible(true);
- task.setHasBeenVisible(true);
- task.onConfigurationChanged(task.getParent().getConfiguration());
-
- verify(persister, never()).saveTask(same(task), any());
- verify(persister).saveTask(same(leafTask), any());
- }
-
- @Test
- public void testNotSpecifyOrientationByFloatingTask() {
- final Task task = new TaskBuilder(mSupervisor)
- .setCreateActivity(true).setCreateParentTask(true).build();
- final ActivityRecord activity = task.getTopMostActivity();
- final WindowContainer<?> parentContainer = task.getParent();
- final TaskDisplayArea taskDisplayArea = task.getDisplayArea();
- activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parentContainer.getOrientation());
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
-
- task.setWindowingMode(WINDOWING_MODE_PINNED);
-
- // TDA returns the last orientation when child returns UNSET
- assertEquals(SCREEN_ORIENTATION_UNSET, parentContainer.getOrientation());
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
- }
-
- @Test
- public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() {
- final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
- final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
- mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
- FEATURE_VENDOR_FIRST);
- final Task firstStack = firstTaskDisplayArea.createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final Task secondStack = secondTaskDisplayArea.createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(firstStack).build();
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
- .setTask(secondStack).build();
- firstActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- secondActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
-
- // Activity on TDA1 is focused
- mDisplayContent.setFocusedApp(firstActivity);
-
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation());
- assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation());
-
- // No focused app, TDA1 is still recorded as last focused.
- mDisplayContent.setFocusedApp(null);
-
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation());
- assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation());
-
- // Activity on TDA2 is focused
- mDisplayContent.setFocusedApp(secondActivity);
-
- assertEquals(SCREEN_ORIENTATION_UNSET, firstTaskDisplayArea.getOrientation());
- assertEquals(SCREEN_ORIENTATION_PORTRAIT, secondTaskDisplayArea.getOrientation());
- }
-
- @Test
- public void testNotifyOrientationChangeCausedByConfigurationChange() {
- final Task task = getTestTask();
- final ActivityRecord activity = task.getTopMostActivity();
- final DisplayContent display = task.getDisplayContent();
- display.setWindowingMode(WINDOWING_MODE_FREEFORM);
-
- activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
- verify(display).onDescendantOrientationChanged(same(task));
- reset(display);
-
- display.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, task.getOrientation());
- verify(display).onDescendantOrientationChanged(same(task));
- }
-
- private Task getTestTask() {
- final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
- return stack.getBottomMostTask();
- }
-
- private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
- Rect expectedConfigBounds) {
-
- TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
- Task stack = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build();
-
- final Configuration parentConfig = stack.getConfiguration();
- parentConfig.windowConfiguration.setAppBounds(parentBounds);
- task.setBounds(bounds);
-
- task.resolveOverrideConfiguration(parentConfig);
- // Assert that both expected and actual are null or are equal to each other
- assertEquals(expectedConfigBounds,
- task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds());
- }
-
- private byte[] serializeToBytes(Task r) throws Exception {
- try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
- final TypedXmlSerializer serializer = Xml.newFastSerializer();
- serializer.setOutput(os, "UTF-8");
- serializer.startDocument(null, true);
- serializer.startTag(null, TASK_TAG);
- r.saveToXml(serializer);
- serializer.endTag(null, TASK_TAG);
- serializer.endDocument();
-
- os.flush();
- return os.toByteArray();
- }
- }
-
- private Task restoreFromBytes(byte[] in) throws IOException, XmlPullParserException {
- try (Reader reader = new InputStreamReader(new ByteArrayInputStream(in))) {
- final TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(reader);
- assertEquals(XmlPullParser.START_TAG, parser.next());
- assertEquals(TASK_TAG, parser.getName());
- return Task.restoreFromXml(parser, mAtm.mTaskSupervisor);
- }
- }
-
- private Task createTask(int taskId) {
- return new Task.Builder(mAtm)
- .setTaskId(taskId)
- .setIntent(new Intent())
- .setRealActivity(ActivityBuilder.getDefaultComponent())
- .setEffectiveUid(10050)
- .buildInner();
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
deleted file mode 100644
index e58c162..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-
-import android.app.WindowConfiguration;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link Task} class.
- *
- * Build/Install/Run:
- * atest WmTests:TaskStackTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class TaskStackTests extends WindowTestsBase {
-
- @Test
- public void testStackPositionChildAt() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task1 = createTaskInStack(stack, 0 /* userId */);
- final Task task2 = createTaskInStack(stack, 1 /* userId */);
-
- // Current user task should be moved to top.
- stack.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */);
- assertEquals(stack.mChildren.get(0), task2);
- assertEquals(stack.mChildren.get(1), task1);
-
- // Non-current user won't be moved to top.
- stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */);
- assertEquals(stack.mChildren.get(0), task2);
- assertEquals(stack.mChildren.get(1), task1);
-
- // Non-leaf task should be moved to top regardless of the user id.
- createTaskInStack(task2, 0 /* userId */);
- createTaskInStack(task2, 1 /* userId */);
- stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */);
- assertEquals(stack.mChildren.get(0), task1);
- assertEquals(stack.mChildren.get(1), task2);
- }
-
- @Test
- public void testClosingAppDifferentTaskOrientation() {
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
-
- final WindowContainer parent = activity1.getTask().getParent();
- assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation());
- mDisplayContent.mClosingApps.add(activity2);
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parent.getOrientation());
- }
-
- @Test
- public void testMoveTaskToBackDifferentTaskOrientation() {
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
- activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
- activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
-
- final WindowContainer parent = activity1.getTask().getParent();
- assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation());
- }
-
- @Test
- public void testStackRemoveImmediately() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
- assertEquals(stack, task.getRootTask());
-
- // Remove stack and check if its child is also removed.
- stack.removeImmediately();
- assertNull(stack.getDisplayContent());
- assertNull(task.getParent());
- }
-
- @Test
- public void testRemoveContainer() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
-
- assertNotNull(stack);
- assertNotNull(task);
- stack.removeIfPossible();
- // Assert that the container was removed.
- assertNull(stack.getParent());
- assertEquals(0, stack.getChildCount());
- assertNull(stack.getDisplayContent());
- assertNull(task.getDisplayContent());
- assertNull(task.getParent());
- }
-
- @Test
- public void testRemoveContainer_deferRemoval() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
-
- // Stack removal is deferred if one of its child is animating.
- doReturn(true).when(stack).hasWindowsAlive();
- doReturn(stack).when(task).getAnimatingContainer(
- eq(TRANSITION | CHILDREN), anyInt());
-
- stack.removeIfPossible();
- // For the case of deferred removal the task controller will still be connected to the its
- // container until the stack window container is removed.
- assertNotNull(stack.getParent());
- assertNotEquals(0, stack.getChildCount());
- assertNotNull(task);
-
- stack.removeImmediately();
- // After removing, the task will be isolated.
- assertNull(task.getParent());
- assertEquals(0, task.getChildCount());
- }
-
- @Test
- public void testReparent() {
- // Create first stack on primary display.
- final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
- final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-
- // Create second display and put second stack on it.
- final DisplayContent dc = createNewDisplay();
- final Task stack2 = createTaskStackOnDisplay(dc);
-
- // Reparent
- clearInvocations(task1); // reset the number of onDisplayChanged for task.
- stack1.reparent(dc.getDefaultTaskDisplayArea(), true /* onTop */);
- assertEquals(dc, stack1.getDisplayContent());
- final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
- final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
- assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
- verify(task1, times(1)).onDisplayChanged(any());
- }
-
- @Test
- public void testStackOutset() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final int stackOutset = 10;
- spyOn(stack);
- doReturn(stackOutset).when(stack).getTaskOutset();
- doReturn(true).when(stack).inMultiWindowMode();
-
- // Mock the resolved override windowing mode to non-fullscreen
- final WindowConfiguration windowConfiguration =
- stack.getResolvedOverrideConfiguration().windowConfiguration;
- spyOn(windowConfiguration);
- doReturn(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
- .when(windowConfiguration).getWindowingMode();
-
- // Prevent adjust task dimensions
- doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any(), any());
-
- final Rect stackBounds = new Rect(200, 200, 800, 1000);
- // Update surface position and size by the given bounds.
- stack.setBounds(stackBounds);
-
- assertEquals(stackBounds.width() + 2 * stackOutset, stack.getLastSurfaceSize().x);
- assertEquals(stackBounds.height() + 2 * stackOutset, stack.getLastSurfaceSize().y);
- assertEquals(stackBounds.left - stackOutset, stack.getLastSurfacePosition().x);
- assertEquals(stackBounds.top - stackOutset, stack.getLastSurfacePosition().y);
- }
-
- @Test
- public void testActivityAndTaskGetsProperType() {
- final Task task1 = new TaskBuilder(mSupervisor).build();
- ActivityRecord activity1 = createNonAttachedActivityRecord(mDisplayContent);
-
- // First activity should become standard
- task1.addChild(activity1, 0);
- assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity1.getActivityType());
- assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
-
- // Second activity should also become standard
- ActivityRecord activity2 = createNonAttachedActivityRecord(mDisplayContent);
- task1.addChild(activity2, WindowContainer.POSITION_TOP);
- assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity2.getActivityType());
- assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index dca6b08..2389d2d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -16,20 +16,39 @@
package com.android.server.wm;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+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_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
+import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.google.common.truth.Truth.assertThat;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -39,18 +58,45 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+import android.view.DisplayInfo;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
+import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
/**
* Test class for {@link Task}.
@@ -58,15 +104,25 @@
* Build/Install/Run:
* atest WmTests:TaskTests
*/
-@SmallTest
+@MediumTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class TaskTests extends WindowTestsBase {
+ private static final String TASK_TAG = "task";
+
+ private Rect mParentBounds;
+
+ @Before
+ public void setUp() throws Exception {
+ mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
+ removeGlobalMinSizeRestriction();
+ }
+
@Test
public void testRemoveContainer() {
- final Task stackController1 = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stackController1, 0 /* userId */);
+ final Task taskController1 = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(taskController1, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
task.removeIfPossible();
@@ -78,8 +134,8 @@
@Test
public void testRemoveContainer_deferRemoval() {
- final Task stackController1 = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stackController1, 0 /* userId */);
+ final Task taskController1 = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(taskController1, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
doReturn(true).when(task).shouldDeferRemoval();
@@ -99,14 +155,14 @@
@Test
public void testReparent() {
- final Task stackController1 = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stackController1, 0 /* userId */);
- final Task stackController2 = createTaskStackOnDisplay(mDisplayContent);
- final Task task2 = createTaskInStack(stackController2, 0 /* userId */);
+ final Task taskController1 = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(taskController1, 0 /* userId */);
+ final Task taskController2 = createTask(mDisplayContent);
+ final Task task2 = createTaskInRootTask(taskController2, 0 /* userId */);
boolean gotException = false;
try {
- task.reparent(stackController1, 0, false/* moveParents */, "testReparent");
+ task.reparent(taskController1, 0, false/* moveParents */, "testReparent");
} catch (IllegalArgumentException e) {
gotException = true;
}
@@ -118,29 +174,29 @@
} catch (Exception e) {
gotException = true;
}
- assertTrue("Should not be able to reparent to a stack that doesn't exist", gotException);
+ assertTrue("Should not be able to reparent to a task that doesn't exist", gotException);
- task.reparent(stackController2, 0, false/* moveParents */, "testReparent");
- assertEquals(stackController2, task.getParent());
+ task.reparent(taskController2, 0, false/* moveParents */, "testReparent");
+ assertEquals(taskController2, task.getParent());
assertEquals(0, task.getParent().mChildren.indexOf(task));
assertEquals(1, task2.getParent().mChildren.indexOf(task2));
}
@Test
public void testReparent_BetweenDisplays() {
- // Create first stack on primary display.
- final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack1, 0 /* userId */);
- assertEquals(mDisplayContent, stack1.getDisplayContent());
+ // Create first task on primary display.
+ final Task rootTask1 = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask1, 0 /* userId */);
+ assertEquals(mDisplayContent, rootTask1.getDisplayContent());
- // Create second display and put second stack on it.
+ // Create second display and put second task on it.
final DisplayContent dc = createNewDisplay();
- final Task stack2 = createTaskStackOnDisplay(dc);
- final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+ final Task rootTask2 = createTask(dc);
+ final Task task2 = createTaskInRootTask(rootTask2, 0 /* userId */);
// Reparent and check state
clearInvocations(task); // reset the number of onDisplayChanged for task.
- task.reparent(stack2, 0, false /* moveParents */, "testReparent_BetweenDisplays");
- assertEquals(stack2, task.getParent());
+ task.reparent(rootTask2, 0, false /* moveParents */, "testReparent_BetweenDisplays");
+ assertEquals(rootTask2, task.getParent());
assertEquals(0, task.getParent().mChildren.indexOf(task));
assertEquals(1, task2.getParent().mChildren.indexOf(task2));
verify(task, times(1)).onDisplayChanged(any());
@@ -148,8 +204,8 @@
@Test
public void testBounds() {
- final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack1, 0 /* userId */);
+ final Task rootTask1 = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask1, 0 /* userId */);
// Check that setting bounds also updates surface position
task.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -159,9 +215,9 @@
}
@Test
- public void testIsInStack() {
- final Task task1 = createTaskStackOnDisplay(mDisplayContent);
- final Task task2 = createTaskStackOnDisplay(mDisplayContent);
+ public void testIsInTask() {
+ final Task task1 = createTask(mDisplayContent);
+ final Task task2 = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task1);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task2);
assertEquals(activity1, task1.isInTask(activity1));
@@ -170,7 +226,7 @@
@Test
public void testRemoveChildForOverlayTask() {
- final Task task = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTask(mDisplayContent);
final int taskId = task.mTaskId;
final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
@@ -193,10 +249,10 @@
@Test
public void testSwitchUser() {
- final Task rootTask = createTaskStackOnDisplay(mDisplayContent);
- final Task childTask = createTaskInStack(rootTask, 0 /* userId */);
- final Task leafTask1 = createTaskInStack(childTask, 10 /* userId */);
- final Task leafTask2 = createTaskInStack(childTask, 0 /* userId */);
+ final Task rootTask = createTask(mDisplayContent);
+ final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
+ final Task leafTask1 = createTaskInRootTask(childTask, 10 /* userId */);
+ final Task leafTask2 = createTaskInRootTask(childTask, 0 /* userId */);
assertEquals(1, rootTask.getChildCount());
assertEquals(leafTask2, childTask.getTopChild());
@@ -208,9 +264,9 @@
@Test
public void testEnsureActivitiesVisible() {
- final Task rootTask = createTaskStackOnDisplay(mDisplayContent);
- final Task leafTask1 = createTaskInStack(rootTask, 0 /* userId */);
- final Task leafTask2 = createTaskInStack(rootTask, 0 /* userId */);
+ final Task rootTask = createTask(mDisplayContent);
+ final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */);
+ final Task leafTask2 = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity1 = createActivityRecord(mDisplayContent, leafTask1);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent, leafTask2);
@@ -233,7 +289,7 @@
@Test
public void testResolveNonResizableTaskWindowingMode() {
- final Task task = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTask(mDisplayContent);
Configuration parentConfig = task.getParent().getConfiguration();
parentConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
doReturn(false).when(task).isResizeable();
@@ -264,10 +320,10 @@
@Test
public void testHandlesOrientationChangeFromDescendant() {
- final Task rootTask = createTaskStackOnDisplay(WINDOWING_MODE_MULTI_WINDOW,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task leafTask1 = createTaskInStack(rootTask, 0 /* userId */);
- final Task leafTask2 = createTaskInStack(rootTask, 0 /* userId */);
+ final Task rootTask = createTask(mDisplayContent,
+ WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */);
+ final Task leafTask2 = createTaskInRootTask(rootTask, 0 /* userId */);
leafTask1.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_HOME);
leafTask2.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_STANDARD);
@@ -281,7 +337,7 @@
@Test
public void testAlwaysOnTop() {
- final Task task = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTask(mDisplayContent);
task.setAlwaysOnTop(true);
task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
assertTrue(task.isAlwaysOnTop());
@@ -289,4 +345,1053 @@
task.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */);
assertFalse(task.isAlwaysOnTop());
}
+
+ @Test
+ public void testRestoreWindowedTask() throws Exception {
+ final Task expected = createTask(64);
+ expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100);
+
+ final byte[] serializedBytes = serializeToBytes(expected);
+ final Task actual = restoreFromBytes(serializedBytes);
+ assertEquals(expected.mTaskId, actual.mTaskId);
+ assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds);
+ }
+
+ /** Ensure we have no chance to modify the original intent. */
+ @Test
+ public void testCopyBaseIntentForTaskInfo() {
+ final Task task = createTask(1);
+ task.setTaskDescription(new ActivityManager.TaskDescription());
+ final TaskInfo info = task.getTaskInfo();
+
+ // The intent of info should be a copy so assert that they are different instances.
+ Assert.assertThat(info.baseIntent, not(sameInstance(task.getBaseIntent())));
+ }
+
+ @Test
+ public void testReturnsToHomeRootTask() throws Exception {
+ final Task task = createTask(1);
+ spyOn(task);
+ doReturn(true).when(task).hasChild();
+ assertFalse(task.returnsToHomeRootTask());
+ task.intent = null;
+ assertFalse(task.returnsToHomeRootTask());
+ task.intent = new Intent();
+ assertFalse(task.returnsToHomeRootTask());
+ task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
+ assertTrue(task.returnsToHomeRootTask());
+ }
+
+ /** Ensures that empty bounds cause appBounds to inherit from parent. */
+ @Test
+ public void testAppBounds_EmptyBounds() {
+ final Rect emptyBounds = new Rect();
+ testRootTaskBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
+ mParentBounds);
+ }
+
+ /** Ensures that bounds on freeform root tasks are not clipped. */
+ @Test
+ public void testAppBounds_FreeFormBounds() {
+ final Rect freeFormBounds = new Rect(mParentBounds);
+ freeFormBounds.offset(10, 10);
+ testRootTaskBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
+ freeFormBounds);
+ }
+
+ /** Ensures that fully contained bounds are not clipped. */
+ @Test
+ public void testAppBounds_ContainedBounds() {
+ final Rect insetBounds = new Rect(mParentBounds);
+ insetBounds.inset(5, 5, 5, 5);
+ testRootTaskBoundsConfiguration(
+ WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds);
+ }
+
+ @Test
+ public void testFitWithinBounds() {
+ final Rect parentBounds = new Rect(10, 10, 200, 200);
+ TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
+ Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM,
+ ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ final Configuration parentConfig = rootTask.getConfiguration();
+ parentConfig.windowConfiguration.setBounds(parentBounds);
+ parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+
+ // check top and left
+ Rect reqBounds = new Rect(-190, -190, 0, 0);
+ task.setBounds(reqBounds);
+ // Make sure part of it is exposed
+ assertTrue(task.getBounds().right > parentBounds.left);
+ assertTrue(task.getBounds().bottom > parentBounds.top);
+ // Should still be more-or-less in that corner
+ assertTrue(task.getBounds().left <= parentBounds.left);
+ assertTrue(task.getBounds().top <= parentBounds.top);
+
+ assertEquals(reqBounds.width(), task.getBounds().width());
+ assertEquals(reqBounds.height(), task.getBounds().height());
+
+ // check bottom and right
+ reqBounds = new Rect(210, 210, 400, 400);
+ task.setBounds(reqBounds);
+ // Make sure part of it is exposed
+ assertTrue(task.getBounds().left < parentBounds.right);
+ assertTrue(task.getBounds().top < parentBounds.bottom);
+ // Should still be more-or-less in that corner
+ assertTrue(task.getBounds().right >= parentBounds.right);
+ assertTrue(task.getBounds().bottom >= parentBounds.bottom);
+
+ assertEquals(reqBounds.width(), task.getBounds().width());
+ assertEquals(reqBounds.height(), task.getBounds().height());
+ }
+
+ /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
+ @Test
+ public void testBoundsOnModeChangeFreeformToFullscreen() {
+ DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
+ Task rootTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ Task task = rootTask.getBottomMostTask();
+ task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+ DisplayInfo info = new DisplayInfo();
+ display.mDisplay.getDisplayInfo(info);
+ final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
+ final Rect freeformBounds = new Rect(fullScreenBounds);
+ freeformBounds.inset((int) (freeformBounds.width() * 0.2),
+ (int) (freeformBounds.height() * 0.2));
+ task.setBounds(freeformBounds);
+
+ assertEquals(freeformBounds, task.getBounds());
+
+ // FULLSCREEN inherits bounds
+ rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertEquals(fullScreenBounds, task.getBounds());
+ assertEquals(freeformBounds, task.mLastNonFullscreenBounds);
+
+ // FREEFORM restores bounds
+ rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(freeformBounds, task.getBounds());
+ }
+
+ /**
+ * Tests that a task with forced orientation has orientation-consistent bounds within the
+ * parent.
+ */
+ @Test
+ public void testFullscreenBoundsForcedOrientation() {
+ final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
+ final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm,
+ fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build();
+ assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId));
+ // Fix the display orientation to landscape which is the natural rotation (0) for the test
+ // display.
+ final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
+ dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
+ dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
+
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+ final Task task = rootTask.getBottomMostTask();
+ final ActivityRecord root = task.getTopNonFinishingActivity();
+
+ assertEquals(fullScreenBounds, task.getBounds());
+
+ // Setting app to fixed portrait fits within parent
+ root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(root, task.getRootActivity());
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
+ // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds.
+ assertThat(task.getBounds().height()).isLessThan(task.getBounds().width());
+ assertEquals(fullScreenBounds, task.getBounds());
+
+ // Top activity gets used
+ final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(rootTask)
+ .build();
+ assertEquals(top, task.getTopNonFinishingActivity());
+ top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
+ assertEquals(task.getBounds().width(), fullScreenBounds.width());
+
+ // Setting app to unspecified restores
+ top.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(fullScreenBounds, task.getBounds());
+
+ // Setting app to fixed landscape and changing display
+ top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ // Fix the display orientation to portrait which is 90 degrees for the test display.
+ dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90);
+
+ // Fixed orientation request should be resolved on activity level. Task fills display
+ // bounds.
+ assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width());
+ assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height());
+ assertEquals(fullScreenBoundsPort, task.getBounds());
+
+ // in FREEFORM, no constraint
+ final Rect freeformBounds = new Rect(display.getBounds());
+ freeformBounds.inset((int) (freeformBounds.width() * 0.2),
+ (int) (freeformBounds.height() * 0.2));
+ rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.setBounds(freeformBounds);
+ assertEquals(freeformBounds, task.getBounds());
+
+ // FULLSCREEN letterboxes bounds on activity level, no constraint on task level.
+ rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width());
+ assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height());
+ assertEquals(fullScreenBoundsPort, task.getBounds());
+
+ // FREEFORM restores bounds as before
+ rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(freeformBounds, task.getBounds());
+ }
+
+ @Test
+ public void testReportsOrientationRequestInLetterboxForOrientation() {
+ final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
+ final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm,
+ fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build();
+ assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId));
+ // Fix the display orientation to landscape which is the natural rotation (0) for the test
+ // display.
+ final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
+ dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
+ dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
+
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+ final Task task = rootTask.getBottomMostTask();
+ ActivityRecord root = task.getTopNonFinishingActivity();
+
+ assertEquals(fullScreenBounds, task.getBounds());
+
+ // Setting app to fixed portrait fits within parent on activity level. Task fills parent.
+ root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ assertThat(root.getBounds().width()).isLessThan(root.getBounds().height());
+ assertEquals(task.getBounds(), fullScreenBounds);
+
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation());
+ }
+
+ @Test
+ public void testIgnoresForcedOrientationWhenParentHandles() {
+ final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
+ DisplayContent display = new TestDisplayContent.Builder(
+ mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
+
+ display.getRequestedOverrideConfiguration().orientation =
+ Configuration.ORIENTATION_LANDSCAPE;
+ display.onRequestedOverrideConfigurationChanged(
+ display.getRequestedOverrideConfiguration());
+ Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+ Task task = rootTask.getBottomMostTask();
+ ActivityRecord root = task.getTopNonFinishingActivity();
+
+ final WindowContainer parentWindowContainer =
+ new WindowContainer(mSystemServicesTestRule.getWindowManagerService());
+ spyOn(parentWindowContainer);
+ parentWindowContainer.setBounds(fullScreenBounds);
+ doReturn(parentWindowContainer).when(task).getParent();
+ doReturn(display.getDefaultTaskDisplayArea()).when(task).getDisplayArea();
+ doReturn(rootTask).when(task).getRootTask();
+ doReturn(true).when(parentWindowContainer).handlesOrientationChangeFromDescendant();
+
+ // Setting app to fixed portrait fits within parent, but Task shouldn't adjust the
+ // bounds because its parent says it will handle it at a later time.
+ root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(root, task.getRootActivity());
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
+ assertEquals(fullScreenBounds, task.getBounds());
+ }
+
+ @Test
+ public void testComputeConfigResourceOverrides() {
+ final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920);
+ TestDisplayContent display = new TestDisplayContent.Builder(
+ mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+ final Configuration inOutConfig = new Configuration();
+ final Configuration parentConfig = new Configuration();
+ final int longSide = 1200;
+ final int shortSide = 600;
+ final Rect parentBounds = new Rect(0, 0, 250, 500);
+ final Rect parentAppBounds = new Rect(0, 0, 250, 480);
+ parentConfig.windowConfiguration.setBounds(parentBounds);
+ parentConfig.windowConfiguration.setAppBounds(parentAppBounds);
+ parentConfig.densityDpi = 400;
+ parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200
+ parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100
+ parentConfig.windowConfiguration.setRotation(ROTATION_0);
+
+ // By default, the input bounds will fill parent.
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+ assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
+ assertEquals(parentAppBounds, inOutConfig.windowConfiguration.getAppBounds());
+ assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation);
+
+ // If bounds are overridden, config properties should be made to match. Surface hierarchy
+ // will crop for policy.
+ inOutConfig.setToDefaults();
+ final Rect largerPortraitBounds = new Rect(0, 0, shortSide, longSide);
+ inOutConfig.windowConfiguration.setBounds(largerPortraitBounds);
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+ // The override bounds are beyond the parent, the out appBounds should not be intersected
+ // by parent appBounds.
+ assertEquals(largerPortraitBounds, inOutConfig.windowConfiguration.getAppBounds());
+ assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160);
+ assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160);
+
+ inOutConfig.setToDefaults();
+ // Landscape bounds.
+ final Rect largerLandscapeBounds = new Rect(0, 0, longSide, shortSide);
+ inOutConfig.windowConfiguration.setBounds(largerLandscapeBounds);
+
+ // Setup the display with a top stable inset. The later assertion will ensure the inset is
+ // excluded from screenHeightDp.
+ final int statusBarHeight = 100;
+ final DisplayPolicy policy = display.getDisplayPolicy();
+ doAnswer(invocationOnMock -> {
+ final Rect insets = invocationOnMock.<Rect>getArgument(0);
+ insets.top = statusBarHeight;
+ return null;
+ }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
+
+ // Without limiting to be inside the parent bounds, the out screen size should keep relative
+ // to the input bounds.
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord.CompatDisplayInsets compatIntsets =
+ new ActivityRecord.CompatDisplayInsets(
+ display, activity, /* fixedOrientationBounds= */ null);
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets);
+
+ assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
+ assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi,
+ inOutConfig.screenHeightDp);
+ assertEquals(longSide * DENSITY_DEFAULT / parentConfig.densityDpi,
+ inOutConfig.screenWidthDp);
+ assertEquals(Configuration.ORIENTATION_LANDSCAPE, inOutConfig.orientation);
+ }
+
+ @Test
+ public void testComputeConfigResourceLayoutOverrides() {
+ final Rect fullScreenBounds = new Rect(0, 0, 1000, 2500);
+ TestDisplayContent display = new TestDisplayContent.Builder(
+ mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+ final Configuration inOutConfig = new Configuration();
+ final Configuration parentConfig = new Configuration();
+ final Rect nonLongBounds = new Rect(0, 0, 1000, 1250);
+ parentConfig.windowConfiguration.setBounds(fullScreenBounds);
+ parentConfig.windowConfiguration.setAppBounds(fullScreenBounds);
+ parentConfig.densityDpi = 400;
+ parentConfig.screenHeightDp = (fullScreenBounds.bottom * 160) / parentConfig.densityDpi;
+ parentConfig.screenWidthDp = (fullScreenBounds.right * 160) / parentConfig.densityDpi;
+ parentConfig.windowConfiguration.setRotation(ROTATION_0);
+
+ // Set BOTH screenW/H to an override value
+ inOutConfig.screenWidthDp = nonLongBounds.width() * 160 / parentConfig.densityDpi;
+ inOutConfig.screenHeightDp = nonLongBounds.height() * 160 / parentConfig.densityDpi;
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ // screenLayout should honor override when both screenW/H are set.
+ assertTrue((inOutConfig.screenLayout & Configuration.SCREENLAYOUT_LONG_NO) != 0);
+ }
+
+ @Test
+ public void testComputeNestedConfigResourceOverrides() {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ assertTrue(task.getResolvedOverrideBounds().isEmpty());
+ int origScreenH = task.getConfiguration().screenHeightDp;
+ Configuration rootTaskConfig = new Configuration();
+ rootTaskConfig.setTo(task.getRootTask().getRequestedOverrideConfiguration());
+ rootTaskConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ // Set bounds on root task (not task) and verify that the task resource configuration
+ // changes despite it's override bounds being empty.
+ Rect bounds = new Rect(task.getRootTask().getBounds());
+ bounds.bottom = (int) (bounds.bottom * 0.6f);
+ rootTaskConfig.windowConfiguration.setBounds(bounds);
+ task.getRootTask().onRequestedOverrideConfigurationChanged(rootTaskConfig);
+ assertNotEquals(origScreenH, task.getConfiguration().screenHeightDp);
+ }
+
+ @Test
+ public void testFullScreenTaskNotAdjustedByMinimalSize() {
+ final Task fullscreenTask = new TaskBuilder(mSupervisor).build();
+ final Rect originalTaskBounds = new Rect(fullscreenTask.getBounds());
+ final ActivityInfo aInfo = new ActivityInfo();
+ aInfo.windowLayout = new ActivityInfo.WindowLayout(0 /* width */, 0 /* widthFraction */,
+ 0 /* height */, 0 /* heightFraction */, 0 /* gravity */,
+ originalTaskBounds.width() * 2 /* minWidth */,
+ originalTaskBounds.height() * 2 /* minHeight */);
+ fullscreenTask.setMinDimensions(aInfo);
+ fullscreenTask.onConfigurationChanged(fullscreenTask.getParent().getConfiguration());
+
+ assertEquals(originalTaskBounds, fullscreenTask.getBounds());
+ }
+
+ @Test
+ public void testInsetDisregardedWhenFreeformOverlapsNavBar() {
+ TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
+ Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ DisplayInfo displayInfo = new DisplayInfo();
+ mAtm.mContext.getDisplay().getDisplayInfo(displayInfo);
+ final int displayHeight = displayInfo.logicalHeight;
+ final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ final Configuration inOutConfig = new Configuration();
+ final Configuration parentConfig = new Configuration();
+ final int longSide = 1200;
+ final int shortSide = 600;
+ parentConfig.densityDpi = 400;
+ parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px
+ parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
+ parentConfig.windowConfiguration.setRotation(ROTATION_0);
+
+ final int longSideDp = 480; // longSide / density = 1200 / 400 * 160
+ final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160
+ final int screenLayout = parentConfig.screenLayout
+ & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
+ final int reducedScreenLayout =
+ Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp);
+
+ // Portrait bounds overlapping with navigation bar, without insets.
+ final Rect freeformBounds = new Rect(0,
+ displayHeight - 10 - longSide,
+ shortSide,
+ displayHeight - 10);
+ inOutConfig.windowConfiguration.setBounds(freeformBounds);
+ // Set to freeform mode to verify bug fix.
+ inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ // screenW/H should not be effected by parent since overridden and freeform
+ assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenWidthDp);
+ assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenHeightDp);
+ assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
+
+ inOutConfig.setToDefaults();
+ // Landscape bounds overlapping with navigtion bar, without insets.
+ freeformBounds.set(0,
+ displayHeight - 10 - shortSide,
+ longSide,
+ displayHeight - 10);
+ inOutConfig.windowConfiguration.setBounds(freeformBounds);
+ inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenWidthDp);
+ assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenHeightDp);
+ assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
+ }
+
+ /** Ensures that the alias intent won't have target component resolved. */
+ @Test
+ public void testTaskIntentActivityAlias() {
+ final String aliasClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".aliasActivity";
+ final String targetClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".targetActivity";
+ final ComponentName aliasComponent =
+ new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasClassName);
+ final ComponentName targetComponent =
+ new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, targetClassName);
+
+ final Intent intent = new Intent();
+ intent.setComponent(aliasComponent);
+ final ActivityInfo info = new ActivityInfo();
+ info.applicationInfo = new ApplicationInfo();
+ info.packageName = DEFAULT_COMPONENT_PACKAGE_NAME;
+ info.targetActivity = targetClassName;
+
+ final Task task = new Task.Builder(mAtm)
+ .setTaskId(1)
+ .setActivityInfo(info)
+ .setIntent(intent)
+ .build();
+ assertEquals("The alias activity component should be saved in task intent.", aliasClassName,
+ task.intent.getComponent().getClassName());
+
+ ActivityRecord aliasActivity = new ActivityBuilder(mAtm).setComponent(
+ aliasComponent).setTargetActivity(targetClassName).build();
+ assertEquals("Should be the same intent filter.", true,
+ task.isSameIntentFilter(aliasActivity));
+
+ ActivityRecord targetActivity = new ActivityBuilder(mAtm).setComponent(
+ targetComponent).build();
+ assertEquals("Should be the same intent filter.", true,
+ task.isSameIntentFilter(targetActivity));
+
+ ActivityRecord defaultActivity = new ActivityBuilder(mAtm).build();
+ assertEquals("Should not be the same intent filter.", false,
+ task.isSameIntentFilter(defaultActivity));
+ }
+
+ /** Test that root activity index is reported correctly for several activities in the task. */
+ @Test
+ public void testFindRootIndex() {
+ final Task task = getTestTask();
+ // Add an extra activity on top of the root one
+ new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals("The root activity in the task must be reported.", task.getChildAt(0),
+ task.getRootActivity(
+ true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
+ }
+
+ /**
+ * Test that root activity index is reported correctly for several activities in the task when
+ * the activities on the bottom are finishing.
+ */
+ @Test
+ public void testFindRootIndex_finishing() {
+ final Task task = getTestTask();
+ // Add extra two activities and mark the two on the bottom as finishing.
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.finishing = true;
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.finishing = true;
+ new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals("The first non-finishing activity in the task must be reported.",
+ task.getChildAt(2), task.getRootActivity(
+ true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
+ }
+
+ /**
+ * Test that root activity index is reported correctly for several activities in the task when
+ * looking for the 'effective root'.
+ */
+ @Test
+ public void testFindRootIndex_effectiveRoot() {
+ final Task task = getTestTask();
+ // Add an extra activity on top of the root one
+ new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals("The root activity in the task must be reported.",
+ task.getChildAt(0), task.getRootActivity(
+ false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
+ }
+
+ /**
+ * Test that root activity index is reported correctly when looking for the 'effective root' in
+ * case when bottom activities are relinquishing task identity or finishing.
+ */
+ @Test
+ public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() {
+ final Task task = getTestTask();
+ // Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and
+ // one above as finishing.
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.finishing = true;
+ new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals("The first non-finishing activity and non-relinquishing task identity "
+ + "must be reported.", task.getChildAt(2), task.getRootActivity(
+ false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
+ }
+
+ /**
+ * Test that root activity index is reported correctly when looking for the 'effective root'
+ * for the case when there is only a single activity that also has relinquishTaskIdentity set.
+ */
+ @Test
+ public void testFindRootIndex_effectiveRoot_relinquishingAndSingleActivity() {
+ final Task task = getTestTask();
+ // Set relinquishTaskIdentity for the only activity in the task
+ task.getBottomMostActivity().info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+
+ assertEquals("The root activity in the task must be reported.",
+ task.getChildAt(0), task.getRootActivity(
+ false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
+ }
+
+ /**
+ * Test that the topmost activity index is reported correctly when looking for the
+ * 'effective root' for the case when all activities have relinquishTaskIdentity set.
+ */
+ @Test
+ public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() {
+ final Task task = getTestTask();
+ // Set relinquishTaskIdentity for all activities in the task
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+
+ assertEquals("The topmost activity in the task must be reported.",
+ task.getChildAt(task.getChildCount() - 1), task.getRootActivity(
+ false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
+ }
+
+ /** Test that bottom-most activity is reported in {@link Task#getRootActivity()}. */
+ @Test
+ public void testGetRootActivity() {
+ final Task task = getTestTask();
+ // Add an extra activity on top of the root one
+ new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals("The root activity in the task must be reported.",
+ task.getBottomMostActivity(), task.getRootActivity());
+ }
+
+ /**
+ * Test that first non-finishing activity is reported in {@link Task#getRootActivity()}.
+ */
+ @Test
+ public void testGetRootActivity_finishing() {
+ final Task task = getTestTask();
+ // Add an extra activity on top of the root one
+ new ActivityBuilder(mAtm).setTask(task).build();
+ // Mark the root as finishing
+ task.getBottomMostActivity().finishing = true;
+
+ assertEquals("The first non-finishing activity in the task must be reported.",
+ task.getChildAt(1), task.getRootActivity());
+ }
+
+ /**
+ * Test that relinquishTaskIdentity flag is ignored in {@link Task#getRootActivity()}.
+ */
+ @Test
+ public void testGetRootActivity_relinquishTaskIdentity() {
+ final Task task = getTestTask();
+ // Mark the bottom-most activity with FLAG_RELINQUISH_TASK_IDENTITY.
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+ // Add an extra activity on top of the root one.
+ new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals("The root activity in the task must be reported.",
+ task.getBottomMostActivity(), task.getRootActivity());
+ }
+
+ /**
+ * Test that no activity is reported in {@link Task#getRootActivity()} when all activities
+ * in the task are finishing.
+ */
+ @Test
+ public void testGetRootActivity_allFinishing() {
+ final Task task = getTestTask();
+ // Mark the bottom-most activity as finishing.
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.finishing = true;
+ // Add an extra activity on top of the root one and mark it as finishing
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.finishing = true;
+
+ assertNull("No activity must be reported if all are finishing", task.getRootActivity());
+ }
+
+ /**
+ * Test that first non-finishing activity is the root of task.
+ */
+ @Test
+ public void testIsRootActivity() {
+ final Task task = getTestTask();
+ // Mark the bottom-most activity as finishing.
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.finishing = true;
+ // Add an extra activity on top of the root one.
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertFalse("Finishing activity must not be the root of task", activity0.isRootOfTask());
+ assertTrue("Non-finishing activity must be the root of task", activity1.isRootOfTask());
+ }
+
+ /**
+ * Test that if all activities in the task are finishing, then the one on the bottom is the
+ * root of task.
+ */
+ @Test
+ public void testIsRootActivity_allFinishing() {
+ final Task task = getTestTask();
+ // Mark the bottom-most activity as finishing.
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.finishing = true;
+ // Add an extra activity on top of the root one and mark it as finishing
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.finishing = true;
+
+ assertTrue("Bottom activity must be the root of task", activity0.isRootOfTask());
+ assertFalse("Finishing activity on top must not be the root of task",
+ activity1.isRootOfTask());
+ }
+
+ /**
+ * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)}.
+ */
+ @Test
+ public void testGetTaskForActivity() {
+ final Task task0 = getTestTask();
+ final ActivityRecord activity0 = task0.getBottomMostActivity();
+
+ final Task task1 = getTestTask();
+ final ActivityRecord activity1 = task1.getBottomMostActivity();
+
+ assertEquals(task0.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
+ assertEquals(task1.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */));
+ }
+
+ /**
+ * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with finishing
+ * activity.
+ */
+ @Test
+ public void testGetTaskForActivity_onlyRoot_finishing() {
+ final Task task = getTestTask();
+ // Make the current root activity finishing
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.finishing = true;
+ // Add an extra activity on top - this will be the new root
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ // Add one more on top
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals(task.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
+ assertEquals(task.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */));
+ assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
+ ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */));
+ }
+
+ /**
+ * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with activity that
+ * relinquishes task identity.
+ */
+ @Test
+ public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() {
+ final Task task = getTestTask();
+ // Make the current root activity relinquish task identity
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+ // Add an extra activity on top - this will be the new root
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ // Add one more on top
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals(task.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
+ assertEquals(task.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */));
+ assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
+ ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */));
+ }
+
+ /**
+ * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} allowing non-root
+ * entries.
+ */
+ @Test
+ public void testGetTaskForActivity_notOnlyRoot() {
+ final Task task = getTestTask();
+ // Mark the bottom-most activity as finishing.
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ activity0.finishing = true;
+
+ // Add an extra activity on top of the root one and make it relinquish task identity
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+
+ // Add one more activity on top
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
+
+ assertEquals(task.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
+ assertEquals(task.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */));
+ assertEquals(task.mTaskId,
+ ActivityRecord.getTaskForActivityLocked(activity2.appToken, false /* onlyRoot */));
+ }
+
+ /**
+ * Test {@link Task#updateEffectiveIntent()}.
+ */
+ @Test
+ public void testUpdateEffectiveIntent() {
+ // Test simple case with a single activity.
+ final Task task = getTestTask();
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+
+ spyOn(task);
+ task.updateEffectiveIntent();
+ verify(task).setIntent(eq(activity0));
+ }
+
+ /**
+ * Test {@link Task#updateEffectiveIntent()} with root activity marked as finishing. This
+ * should make the task use the second activity when updating the intent.
+ */
+ @Test
+ public void testUpdateEffectiveIntent_rootFinishing() {
+ // Test simple case with a single activity.
+ final Task task = getTestTask();
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ // Mark the bottom-most activity as finishing.
+ activity0.finishing = true;
+ // Add an extra activity on top of the root one
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+
+ spyOn(task);
+ task.updateEffectiveIntent();
+ verify(task).setIntent(eq(activity1));
+ }
+
+ /**
+ * Test {@link Task#updateEffectiveIntent()} when all activities are finishing or
+ * relinquishing task identity. In this case the root activity should still be used when
+ * updating the intent (legacy behavior).
+ */
+ @Test
+ public void testUpdateEffectiveIntent_allFinishing() {
+ // Test simple case with a single activity.
+ final Task task = getTestTask();
+ final ActivityRecord activity0 = task.getBottomMostActivity();
+ // Mark the bottom-most activity as finishing.
+ activity0.finishing = true;
+ // Add an extra activity on top of the root one and make it relinquish task identity
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.finishing = true;
+
+ // Task must still update the intent using the root activity (preserving legacy behavior).
+ spyOn(task);
+ task.updateEffectiveIntent();
+ verify(task).setIntent(eq(activity0));
+ }
+
+ @Test
+ public void testSaveLaunchingStateWhenConfigurationChanged() {
+ LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
+ spyOn(persister);
+
+ final Task task = getTestTask();
+ task.setHasBeenVisible(false);
+ task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ task.setHasBeenVisible(true);
+ task.onConfigurationChanged(task.getParent().getConfiguration());
+
+ verify(persister).saveTask(task, task.getDisplayContent());
+ }
+
+ @Test
+ public void testSaveLaunchingStateWhenClearingParent() {
+ LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
+ spyOn(persister);
+
+ final Task task = getTestTask();
+ task.setHasBeenVisible(false);
+ task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final DisplayContent oldDisplay = task.getDisplayContent();
+
+ LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams();
+ params.mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ persister.getLaunchParams(task, null, params);
+ assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode);
+
+ task.setHasBeenVisible(true);
+ task.removeImmediately();
+
+ verify(persister).saveTask(task, oldDisplay);
+
+ persister.getLaunchParams(task, null, params);
+ assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode);
+ }
+
+ @Test
+ public void testNotSaveLaunchingStateNonFreeformDisplay() {
+ LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
+ spyOn(persister);
+
+ final Task task = getTestTask();
+ task.setHasBeenVisible(false);
+ task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ task.setHasBeenVisible(true);
+ task.onConfigurationChanged(task.getParent().getConfiguration());
+
+ Mockito.verify(persister, never()).saveTask(same(task), any());
+ }
+
+ @Test
+ public void testNotSaveLaunchingStateWhenNotFullscreenOrFreeformWindow() {
+ LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
+ spyOn(persister);
+
+ final Task task = getTestTask();
+ task.setHasBeenVisible(false);
+ task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED);
+
+ task.setHasBeenVisible(true);
+ task.onConfigurationChanged(task.getParent().getConfiguration());
+
+ Mockito.verify(persister, never()).saveTask(same(task), any());
+ }
+
+ @Test
+ public void testNotSaveLaunchingStateForNonLeafTask() {
+ LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
+ spyOn(persister);
+
+ final Task task = getTestTask();
+ task.setHasBeenVisible(false);
+ task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ final Task leafTask = createTaskInRootTask(task, 0 /* userId */);
+
+ leafTask.setHasBeenVisible(true);
+ task.setHasBeenVisible(true);
+ task.onConfigurationChanged(task.getParent().getConfiguration());
+
+ Mockito.verify(persister, never()).saveTask(same(task), any());
+ verify(persister).saveTask(same(leafTask), any());
+ }
+
+ @Test
+ public void testNotSpecifyOrientationByFloatingTask() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true).setCreateParentTask(true).build();
+ final ActivityRecord activity = task.getTopMostActivity();
+ final WindowContainer<?> parentContainer = task.getParent();
+ final TaskDisplayArea taskDisplayArea = task.getDisplayArea();
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parentContainer.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
+
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+
+ // TDA returns the last orientation when child returns UNSET
+ assertEquals(SCREEN_ORIENTATION_UNSET, parentContainer.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
+ }
+
+ @Test
+ public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() {
+ final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+ final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
+ mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
+ FEATURE_VENDOR_FIRST);
+ final Task firstRootTask = firstTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final Task secondRootTask = secondTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
+ .setTask(firstRootTask).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
+ .setTask(secondRootTask).build();
+ firstActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ secondActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity on TDA1 is focused
+ mDisplayContent.setFocusedApp(firstActivity);
+
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation());
+
+ // No focused app, TDA1 is still recorded as last focused.
+ mDisplayContent.setFocusedApp(null);
+
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation());
+
+ // Activity on TDA2 is focused
+ mDisplayContent.setFocusedApp(secondActivity);
+
+ assertEquals(SCREEN_ORIENTATION_UNSET, firstTaskDisplayArea.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, secondTaskDisplayArea.getOrientation());
+ }
+
+ @Test
+ public void testNotifyOrientationChangeCausedByConfigurationChange() {
+ final Task task = getTestTask();
+ final ActivityRecord activity = task.getTopMostActivity();
+ final DisplayContent display = task.getDisplayContent();
+ display.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
+ verify(display).onDescendantOrientationChanged(same(task));
+ reset(display);
+
+ display.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, task.getOrientation());
+ verify(display).onDescendantOrientationChanged(same(task));
+ }
+
+ private Task getTestTask() {
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ return task.getBottomMostTask();
+ }
+
+ private void testRootTaskBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
+ Rect expectedConfigBounds) {
+
+ TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
+ Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+
+ final Configuration parentConfig = rootTask.getConfiguration();
+ parentConfig.windowConfiguration.setAppBounds(parentBounds);
+ task.setBounds(bounds);
+
+ task.resolveOverrideConfiguration(parentConfig);
+ // Assert that both expected and actual are null or are equal to each other
+ assertEquals(expectedConfigBounds,
+ task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds());
+ }
+
+ private byte[] serializeToBytes(Task r) throws Exception {
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ final TypedXmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setOutput(os, "UTF-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TASK_TAG);
+ r.saveToXml(serializer);
+ serializer.endTag(null, TASK_TAG);
+ serializer.endDocument();
+
+ os.flush();
+ return os.toByteArray();
+ }
+ }
+
+ private Task restoreFromBytes(byte[] in) throws IOException, XmlPullParserException {
+ try (Reader reader = new InputStreamReader(new ByteArrayInputStream(in))) {
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(reader);
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals(TASK_TAG, parser.getName());
+ return Task.restoreFromXml(parser, mAtm.mTaskSupervisor);
+ }
+ }
+
+ private Task createTask(int taskId) {
+ return new Task.Builder(mAtm)
+ .setTaskId(taskId)
+ .setIntent(new Intent())
+ .setRealActivity(ActivityBuilder.getDefaultComponent())
+ .setEffectiveUid(10050)
+ .buildInner();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 79ef868..2dfb3a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -69,10 +69,8 @@
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
- final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task newTask = createTask(mDisplayContent);
+ final Task oldTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newTask);
// Start states.
@@ -128,12 +126,10 @@
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
- final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task newNestedTask = createTaskInStack(newTask, 0);
- final Task newNestedTask2 = createTaskInStack(newTask, 0);
- final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task newTask = createTask(mDisplayContent);
+ final Task newNestedTask = createTaskInRootTask(newTask, 0);
+ final Task newNestedTask2 = createTaskInRootTask(newTask, 0);
+ final Task oldTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newNestedTask);
final ActivityRecord opening2 = createActivityRecord(newNestedTask2);
@@ -179,11 +175,9 @@
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
- final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task showNestedTask = createTaskInStack(showTask, 0);
- final Task showTask2 = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task showTask = createTask(mDisplayContent);
+ final Task showNestedTask = createTaskInRootTask(showTask, 0);
+ final Task showTask2 = createTask(mDisplayContent);
final DisplayArea tda = showTask.getDisplayArea();
final ActivityRecord showing = createActivityRecord(showNestedTask);
final ActivityRecord showing2 = createActivityRecord(showTask2);
@@ -231,12 +225,10 @@
public void testCreateInfo_existenceChange() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
- final Task openTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task openTask = createTask(mDisplayContent);
final ActivityRecord opening = createActivityRecord(openTask);
opening.mVisibleRequested = false; // starts invisible
- final Task closeTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task closeTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(closeTask);
closing.mVisibleRequested = true; // starts visible
@@ -268,8 +260,8 @@
final Task[] tasks = new Task[taskCount];
for (int i = 0; i < taskCount; ++i) {
// Each add goes on top, so at the end of this, task[9] should be on top
- tasks[i] = createTaskStackOnDisplay(WINDOWING_MODE_FREEFORM,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ tasks[i] = createTask(mDisplayContent,
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
act.mVisibleRequested = (i % 2) == 0; // starts invisible
@@ -305,8 +297,8 @@
final Task[] tasks = new Task[taskCount];
for (int i = 0; i < taskCount; ++i) {
// Each add goes on top, so at the end of this, task[9] should be on top
- tasks[i] = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ tasks[i] = createTask(mDisplayContent,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
act.mVisibleRequested = (i % 2) == 0; // starts invisible
@@ -353,15 +345,13 @@
ArraySet<WindowContainer> participants = transition.mParticipants;
ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
- final Task openTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task openInOpenTask = createTaskInStack(openTask, 0);
+ final Task openTask = createTask(mDisplayContent);
+ final Task openInOpenTask = createTaskInRootTask(openTask, 0);
final ActivityRecord openInOpen = createActivityRecord(openInOpenTask);
- final Task changeTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task changeInChangeTask = createTaskInStack(changeTask, 0);
- final Task openInChangeTask = createTaskInStack(changeTask, 0);
+ final Task changeTask = createTask(mDisplayContent);
+ final Task changeInChangeTask = createTaskInRootTask(changeTask, 0);
+ final Task openInChangeTask = createTaskInRootTask(changeTask, 0);
final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask);
final ActivityRecord openInChange = createActivityRecord(openInChangeTask);
// set organizer for everything so that they all get added to transition info
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index b88173d..6919c4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -811,18 +811,18 @@
@Test
public void testOnDisplayChanged() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final DisplayContent newDc = createNewDisplay();
- stack.getDisplayArea().removeRootTask(stack);
- newDc.getDefaultTaskDisplayArea().addChild(stack, POSITION_TOP);
+ rootTask.getDisplayArea().removeRootTask(rootTask);
+ newDc.getDefaultTaskDisplayArea().addChild(rootTask, POSITION_TOP);
- verify(stack).onDisplayChanged(newDc);
+ verify(rootTask).onDisplayChanged(newDc);
verify(task).onDisplayChanged(newDc);
verify(activity).onDisplayChanged(newDc);
- assertEquals(newDc, stack.mDisplayContent);
+ assertEquals(newDc, rootTask.mDisplayContent);
assertEquals(newDc, task.mDisplayContent);
assertEquals(newDc, activity.mDisplayContent);
}
@@ -854,21 +854,21 @@
@Test
public void testTaskCanApplyAnimation() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
verifyWindowContainerApplyAnimation(task, activity1, activity2);
}
@Test
- public void testStackCanApplyAnimation() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
+ public void testRootTaskCanApplyAnimation() {
+ final Task rootTask = createTask(mDisplayContent);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
- createTaskInStack(stack, 0 /* userId */));
+ createTaskInRootTask(rootTask, 0 /* userId */));
final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
- createTaskInStack(stack, 0 /* userId */));
- verifyWindowContainerApplyAnimation(stack, activity1, activity2);
+ createTaskInRootTask(rootTask, 0 /* userId */));
+ verifyWindowContainerApplyAnimation(rootTask, activity1, activity2);
}
@Test
@@ -878,21 +878,21 @@
assertNull(windowContainer.getDisplayArea());
- // ActivityStack > WindowContainer
- final Task activityStack = createTaskStackOnDisplay(mDisplayContent);
- activityStack.addChild(windowContainer, 0);
- activityStack.setParent(null);
+ // Task > WindowContainer
+ final Task task = createTask(mDisplayContent);
+ task.addChild(windowContainer, 0);
+ task.setParent(null);
assertNull(windowContainer.getDisplayArea());
- assertNull(activityStack.getDisplayArea());
+ assertNull(task.getDisplayArea());
- // TaskDisplayArea > ActivityStack > WindowContainer
+ // TaskDisplayArea > Task > WindowContainer
final TaskDisplayArea taskDisplayArea = new TaskDisplayArea(
mDisplayContent, mWm, "TaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
- taskDisplayArea.addChild(activityStack, 0);
+ taskDisplayArea.addChild(task, 0);
assertEquals(taskDisplayArea, windowContainer.getDisplayArea());
- assertEquals(taskDisplayArea, activityStack.getDisplayArea());
+ assertEquals(taskDisplayArea, task.getDisplayArea());
assertEquals(taskDisplayArea, taskDisplayArea.getDisplayArea());
// DisplayArea
@@ -986,8 +986,8 @@
@Test
public void testFreezeInsets() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final ActivityRecord activity = createActivityRecord(mDisplayContent, stack);
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
// Set visibility to false, verify the main window of the task will be set the frozen
@@ -1002,8 +1002,8 @@
@Test
public void testFreezeInsetsStateWhenAppTransition() {
- final Task stack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 6d0e510..baf072d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -80,18 +80,18 @@
}
@Test
- public void testTaskFocusChange_stackNotHomeType_focusChanges() throws RemoteException {
+ public void testTaskFocusChange_rootTaskNotHomeType_focusChanges() throws RemoteException {
DisplayContent display = createNewDisplay();
// Current focused window
- Task focusedStack = createTaskStackOnDisplay(
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, display);
- Task focusedTask = createTaskInStack(focusedStack, 0 /* userId */);
+ Task focusedRootTask = createTask(
+ display, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+ Task focusedTask = createTaskInRootTask(focusedRootTask, 0 /* userId */);
WindowState focusedWindow = createAppWindow(focusedTask, TYPE_APPLICATION, "App Window");
mDisplayContent.mCurrentFocus = focusedWindow;
// Tapped task
- Task tappedStack = createTaskStackOnDisplay(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, display);
- Task tappedTask = createTaskInStack(tappedStack, 0 /* userId */);
+ Task tappedRootTask = createTask(
+ display, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
spyOn(mWm.mAtmService);
mWm.handleTaskFocusChange(tappedTask);
@@ -100,19 +100,19 @@
}
@Test
- public void testTaskFocusChange_stackHomeTypeWithSameTaskDisplayArea_focusDoesNotChange()
+ public void testTaskFocusChange_rootTaskHomeTypeWithSameTaskDisplayArea_focusDoesNotChange()
throws RemoteException {
DisplayContent display = createNewDisplay();
// Current focused window
- Task focusedStack = createTaskStackOnDisplay(
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, display);
- Task focusedTask = createTaskInStack(focusedStack, 0 /* userId */);
+ Task focusedRootTask = createTask(
+ display, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+ Task focusedTask = createTaskInRootTask(focusedRootTask, 0 /* userId */);
WindowState focusedWindow = createAppWindow(focusedTask, TYPE_APPLICATION, "App Window");
mDisplayContent.mCurrentFocus = focusedWindow;
// Tapped home task
- Task tappedStack = createTaskStackOnDisplay(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, display);
- Task tappedTask = createTaskInStack(tappedStack, 0 /* userId */);
+ Task tappedRootTask = createTask(
+ display, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
+ Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
spyOn(mWm.mAtmService);
mWm.handleTaskFocusChange(tappedTask);
@@ -121,21 +121,21 @@
}
@Test
- public void testTaskFocusChange_stackHomeTypeWithDifferentTaskDisplayArea_focusChanges()
+ public void testTaskFocusChange_rootTaskHomeTypeWithDifferentTaskDisplayArea_focusChanges()
throws RemoteException {
final DisplayContent display = createNewDisplay();
final TaskDisplayArea secondTda = createTaskDisplayArea(
display, mWm, "Tapped TDA", FEATURE_VENDOR_FIRST);
// Current focused window
- Task focusedStack = createTaskStackOnDisplay(
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, display);
- Task focusedTask = createTaskInStack(focusedStack, 0 /* userId */);
+ Task focusedRootTask = createTask(
+ display, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+ Task focusedTask = createTaskInRootTask(focusedRootTask, 0 /* userId */);
WindowState focusedWindow = createAppWindow(focusedTask, TYPE_APPLICATION, "App Window");
mDisplayContent.mCurrentFocus = focusedWindow;
// Tapped home task on another task display area
- Task tappedStack = createTaskStackOnTaskDisplayArea(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, secondTda);
- Task tappedTask = createTaskInStack(tappedStack, 0 /* userId */);
+ Task tappedRootTask = createTask(secondTda, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
+ Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
spyOn(mWm.mAtmService);
mWm.handleTaskFocusChange(tappedTask);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 01c503e..e2b58bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -125,8 +125,8 @@
return registerMockOrganizer(null);
}
- Task createTask(Task stack, boolean fakeDraw) {
- final Task task = createTaskInStack(stack, 0);
+ Task createTask(Task rootTask, boolean fakeDraw) {
+ final Task task = createTaskInRootTask(rootTask, 0);
if (fakeDraw) {
task.setHasBeenVisible(true);
@@ -134,13 +134,13 @@
return task;
}
- Task createTask(Task stack) {
+ Task createTask(Task rootTask) {
// Fake draw notifications for most of our tests.
- return createTask(stack, true);
+ return createTask(rootTask, true);
}
- Task createStack() {
- return createTaskStackOnDisplay(mDisplayContent);
+ Task createRootTask() {
+ return createTask(mDisplayContent);
}
@Before
@@ -153,14 +153,14 @@
@Test
public void testAppearVanish() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- stack.removeImmediately();
+ rootTask.removeImmediately();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskVanished(any());
@@ -169,21 +169,21 @@
@Test
public void testAppearWaitsForVisibility() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack, false);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask, false);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- stack.setHasBeenVisible(true);
+ rootTask.setHasBeenVisible(true);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertTrue(stack.getHasBeenVisible());
+ assertTrue(rootTask.getHasBeenVisible());
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- stack.removeImmediately();
+ rootTask.removeImmediately();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskVanished(any());
@@ -192,108 +192,108 @@
@Test
public void testNoVanishedIfNoAppear() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack, false /* hasBeenVisible */);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask, false /* hasBeenVisible */);
// In this test we skip making the Task visible, and verify
// that even though a TaskOrganizer is set remove doesn't emit
// a vanish callback, because we never emitted appear.
- stack.setTaskOrganizer(organizer);
+ rootTask.setTaskOrganizer(organizer);
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- stack.removeImmediately();
+ rootTask.removeImmediately();
verify(organizer, never()).onTaskVanished(any());
}
@Test
public void testTaskNoDraw() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack, false /* fakeDraw */);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask, false /* fakeDraw */);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTrue(stack.isOrganized());
+ assertTrue(rootTask.isOrganized());
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertTaskVanished(organizer, false /* expectVanished */, stack);
- assertFalse(stack.isOrganized());
+ assertTaskVanished(organizer, false /* expectVanished */, rootTask);
+ assertFalse(rootTask.isOrganized());
}
@Test
public void testClearOrganizer() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTrue(stack.isOrganized());
+ assertTrue(rootTask.isOrganized());
- stack.setTaskOrganizer(null);
+ rootTask.setTaskOrganizer(null);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskVanished(any());
- assertFalse(stack.isOrganized());
+ assertFalse(rootTask.isOrganized());
}
@Test
public void testUnregisterOrganizer() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTrue(stack.isOrganized());
+ assertTrue(rootTask.isOrganized());
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertTaskVanished(organizer, true /* expectVanished */, stack);
- assertFalse(stack.isOrganized());
+ assertTaskVanished(organizer, true /* expectVanished */, rootTask);
+ assertFalse(rootTask.isOrganized());
}
@Test
public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException {
- final Task stack = createStack();
- final Task task = createTask(stack);
- final Task stack2 = createStack();
- final Task task2 = createTask(stack2);
- final Task stack3 = createStack();
- final Task task3 = createTask(stack3);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final Task rootTask2 = createRootTask();
+ final Task task2 = createTask(rootTask2);
+ final Task rootTask3 = createRootTask();
+ final Task task3 = createTask(rootTask3);
final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
// verify that tasks are returned and taskAppeared is not called
- assertContainsTasks(existingTasks, stack, stack2, stack3);
+ assertContainsTasks(existingTasks, rootTask, rootTask2, rootTask3);
verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer, times(0)).onTaskVanished(any());
- assertTrue(stack.isOrganized());
+ assertTrue(rootTask.isOrganized());
// Now we replace the registration and verify the new organizer receives existing tasks
final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>();
final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertContainsTasks(existingTasks2, stack, stack2, stack3);
+ assertContainsTasks(existingTasks2, rootTask, rootTask2, rootTask3);
verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer2, times(0)).onTaskVanished(any());
// Removed tasks from the original organizer
- assertTaskVanished(organizer, true /* expectVanished */, stack, stack2, stack3);
- assertTrue(stack2.isOrganized());
+ assertTaskVanished(organizer, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+ assertTrue(rootTask2.isOrganized());
// Now we unregister the second one, the first one should automatically be reregistered
// so we verify that it's now seeing changes.
@@ -302,18 +302,18 @@
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTaskVanished(organizer2, true /* expectVanished */, stack, stack2, stack3);
+ assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
}
@Test
public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException {
- final Task stack = createStack();
- final Task task = createTask(stack);
- final Task stack2 = createStack();
- final Task task2 = createTask(stack2);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final Task rootTask2 = createRootTask();
+ final Task task2 = createTask(rootTask2);
ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
- assertContainsTasks(existingTasks, stack, stack2);
+ assertContainsTasks(existingTasks, rootTask, rootTask2);
// Verify we don't get onTaskAppeared if we are returned the tasks
verify(organizer, never())
@@ -323,21 +323,21 @@
@Test
public void testTaskTransaction() {
removeGlobalMinSizeRestriction();
- final Task stack = new TaskBuilder(mSupervisor)
+ final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- final Task task = stack.getTopMostTask();
+ final Task task = rootTask.getTopMostTask();
testTransaction(task);
}
@Test
- public void testStackTransaction() {
+ public void testRootTaskTransaction() {
removeGlobalMinSizeRestriction();
- final Task stack = new TaskBuilder(mSupervisor)
+ final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
RootTaskInfo info =
mWm.mAtmService.getRootTaskInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
- assertEquals(stack.mRemoteToken.toWindowContainerToken(), info.token);
- testTransaction(stack);
+ assertEquals(rootTask.mRemoteToken.toWindowContainerToken(), info.token);
+ testTransaction(rootTask);
}
@Test
@@ -357,9 +357,9 @@
@Test
public void testSetWindowingMode() {
- final Task stack = new TaskBuilder(mSupervisor)
+ final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- testSetWindowingMode(stack);
+ testSetWindowingMode(rootTask);
final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea");
displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -376,30 +376,30 @@
@Test
public void testSetActivityWindowingMode() {
final ActivityRecord record = makePipableActivity();
- final Task stack = record.getRootTask();
+ final Task rootTask = record.getRootTask();
final WindowContainerTransaction t = new WindowContainerTransaction();
- t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED);
+ t.setWindowingMode(rootTask.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED);
t.setActivityWindowingMode(
- stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN);
+ rootTask.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertEquals(WINDOWING_MODE_FULLSCREEN, record.getWindowingMode());
- assertEquals(WINDOWING_MODE_PINNED, stack.getWindowingMode());
+ assertEquals(WINDOWING_MODE_PINNED, rootTask.getWindowingMode());
}
@Test
public void testContainerFocusableChanges() {
removeGlobalMinSizeRestriction();
- final Task stack = new TaskBuilder(mSupervisor)
+ final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- final Task task = stack.getTopMostTask();
+ final Task task = rootTask.getTopMostTask();
WindowContainerTransaction t = new WindowContainerTransaction();
assertTrue(task.isFocusable());
- t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), false);
+ t.setFocusable(rootTask.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertFalse(task.isFocusable());
- t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), true);
+ t.setFocusable(rootTask.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertTrue(task.isFocusable());
}
@@ -407,25 +407,25 @@
@Test
public void testContainerHiddenChanges() {
removeGlobalMinSizeRestriction();
- final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
WindowContainerTransaction t = new WindowContainerTransaction();
- assertTrue(stack.shouldBeVisible(null));
- t.setHidden(stack.mRemoteToken.toWindowContainerToken(), true);
+ assertTrue(rootTask.shouldBeVisible(null));
+ t.setHidden(rootTask.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- assertFalse(stack.shouldBeVisible(null));
- t.setHidden(stack.mRemoteToken.toWindowContainerToken(), false);
+ assertFalse(rootTask.shouldBeVisible(null));
+ t.setHidden(rootTask.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- assertTrue(stack.shouldBeVisible(null));
+ assertTrue(rootTask.shouldBeVisible(null));
}
@Test
public void testSetIgnoreOrientationRequest_taskDisplayArea() {
removeGlobalMinSizeRestriction();
final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
- final Task stack = taskDisplayArea.createRootTask(
+ final Task rootTask = taskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
taskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mDisplayContent.setFocusedApp(activity);
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
@@ -461,9 +461,9 @@
public void testSetIgnoreOrientationRequest_displayContent() {
removeGlobalMinSizeRestriction();
final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
- final Task stack = taskDisplayArea.createRootTask(
+ final Task rootTask = taskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
mDisplayContent.setFocusedApp(activity);
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
@@ -491,21 +491,21 @@
@Test
public void testOverrideConfigSize() {
removeGlobalMinSizeRestriction();
- final Task stack = new TaskBuilder(mSupervisor)
+ final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- final Task task = stack.getTopMostTask();
+ final Task task = rootTask.getTopMostTask();
WindowContainerTransaction t = new WindowContainerTransaction();
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
final int origScreenWDp = task.getConfiguration().screenHeightDp;
final int origScreenHDp = task.getConfiguration().screenHeightDp;
t = new WindowContainerTransaction();
// verify that setting config overrides on parent restricts children.
- t.setScreenSizeDp(stack.mRemoteToken
+ t.setScreenSizeDp(rootTask.mRemoteToken
.toWindowContainerToken(), origScreenWDp, origScreenHDp / 2);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertEquals(origScreenHDp / 2, task.getConfiguration().screenHeightDp);
t = new WindowContainerTransaction();
- t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED,
+ t.setScreenSizeDp(rootTask.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED,
SCREEN_HEIGHT_DP_UNDEFINED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
@@ -572,14 +572,14 @@
mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
RunningTaskInfo info1 = task.getTaskInfo();
- final Task stack = createTaskStackOnDisplay(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
- assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode());
+ final Task rootTask = createTask(
+ mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
+ assertEquals(mDisplayContent.getWindowingMode(), rootTask.getWindowingMode());
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
+ wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(info1.configuration.windowConfiguration.getWindowingMode(),
- stack.getWindowingMode());
+ rootTask.getWindowingMode());
// Info should reflect new membership
List<Task> infos = getTasksCreatedByOrganizer(mDisplayContent);
@@ -591,14 +591,14 @@
Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask();
Configuration c = new Configuration(task1.getRequestedOverrideConfiguration());
c.windowConfiguration.setBounds(newSize);
- doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any(), any());
+ doNothing().when(rootTask).adjustForMinimalTaskDimensions(any(), any(), any());
task1.onRequestedOverrideConfigurationChanged(c);
- assertEquals(newSize, stack.getBounds());
+ assertEquals(newSize, rootTask.getBounds());
wct = new WindowContainerTransaction();
- wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
+ wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
- assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode());
+ assertEquals(mDisplayContent.getWindowingMode(), rootTask.getWindowingMode());
infos = getTasksCreatedByOrganizer(mDisplayContent);
info1 = infos.get(0).getTaskInfo();
assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
@@ -644,36 +644,39 @@
lastReportedTiles.clear();
called[0] = false;
- final Task stack = createTaskStackOnDisplay(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task rootTask = createTask(
+ mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask();
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
+ wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
lastReportedTiles.clear();
called[0] = false;
- final Task stack2 = createTaskStackOnDisplay(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
+ final Task rootTask2 = createTask(
+ mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
wct = new WindowContainerTransaction();
- wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
+ wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
+ info1.token, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType);
lastReportedTiles.clear();
called[0] = false;
- task1.positionChildAt(POSITION_TOP, stack, false /* includingParents */);
+ task1.positionChildAt(POSITION_TOP, rootTask, false /* includingParents */);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
lastReportedTiles.clear();
called[0] = false;
wct = new WindowContainerTransaction();
- wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
- wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
+ wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
+ null, true /* onTop */);
+ wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
+ null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
@@ -723,10 +726,10 @@
final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */).size();
- final Task stack = createTaskStackOnDisplay(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
- final Task stack2 = createTaskStackOnDisplay(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
+ final Task rootTask = createTask(
+ mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
+ final Task rootTask2 = createTask(
+ mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
// Check getRootTasks works
List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
@@ -735,8 +738,10 @@
lastReportedTiles.clear();
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
- wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info2.token, true /* onTop */);
+ wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
+ info1.token, true /* onTop */);
+ wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
+ info2.token, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertFalse(lastReportedTiles.isEmpty());
assertEquals(ACTIVITY_TYPE_STANDARD,
@@ -746,7 +751,8 @@
lastReportedTiles.clear();
wct = new WindowContainerTransaction();
- wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, false /* onTop */);
+ wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
+ info1.token, false /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertFalse(lastReportedTiles.isEmpty());
// Standard should still be on top of tile 1, so no change there
@@ -771,7 +777,7 @@
lastReportedTiles.clear();
wct = new WindowContainerTransaction();
- wct.reorder(stack2.mRemoteToken.toWindowContainerToken(), true /* onTop */);
+ wct.reorder(rootTask2.mRemoteToken.toWindowContainerToken(), true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
// Home should now be on top. No change occurs in second tile, so not reported
assertEquals(1, lastReportedTiles.size());
@@ -780,10 +786,10 @@
// This just needs to not crash (ie. it should be possible to reparent to display twice)
wct = new WindowContainerTransaction();
- wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
+ wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
wct = new WindowContainerTransaction();
- wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
+ wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
}
@@ -799,8 +805,8 @@
@Test
public void testBLASTCallbackWithActivityChildren() {
- final Task stackController1 = createStack();
- final Task task = createTask(stackController1);
+ final Task rootTaskController1 = createRootTask();
+ final Task task = createTask(rootTaskController1);
final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
w.mActivityRecord.mVisibleRequested = true;
@@ -926,11 +932,11 @@
ChangeSavingOrganizer o = new ChangeSavingOrganizer();
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o);
- final Task stack = createStack();
- final Task task = createTask(stack);
- final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
assertEquals("TestDescription", o.mChangedInfo.taskDescription.getLabel());
@@ -939,29 +945,29 @@
@Test
public void testPreventDuplicateAppear() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack, false /* fakeDraw */);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask, false /* fakeDraw */);
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- stack.setTaskOrganizer(organizer);
+ rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ rootTask.setTaskOrganizer(organizer);
// setHasBeenVisible was already called once by the set-up code.
- stack.setHasBeenVisible(true);
+ rootTask.setHasBeenVisible(true);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(1))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- stack.setTaskOrganizer(null);
+ rootTask.setTaskOrganizer(null);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(1)).onTaskVanished(any());
- stack.setTaskOrganizer(organizer);
+ rootTask.setTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(2))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- stack.removeImmediately();
+ rootTask.removeImmediately();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(2)).onTaskVanished(any());
@@ -970,15 +976,15 @@
@Test
public void testInterceptBackPressedOnTaskRoot() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
- final ActivityRecord activity = createActivityRecord(stack.mDisplayContent, task);
- final Task stack2 = createStack();
- final Task task2 = createTask(stack2);
- final ActivityRecord activity2 = createActivityRecord(stack.mDisplayContent, task2);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task);
+ final Task rootTask2 = createRootTask();
+ final Task task2 = createTask(rootTask2);
+ final ActivityRecord activity2 = createActivityRecord(rootTask.mDisplayContent, task2);
- assertTrue(stack.isOrganized());
- assertTrue(stack2.isOrganized());
+ assertTrue(rootTask.isOrganized());
+ assertTrue(rootTask2.isOrganized());
// Verify a back pressed does not call the organizer
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
@@ -989,7 +995,7 @@
// Enable intercepting back
mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(
- stack.mRemoteToken.toWindowContainerToken(), true);
+ rootTask.mRemoteToken.toWindowContainerToken(), true);
// Verify now that the back press does call the organizer
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
@@ -1000,7 +1006,7 @@
// Disable intercepting back
mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(
- stack.mRemoteToken.toWindowContainerToken(), false);
+ rootTask.mRemoteToken.toWindowContainerToken(), false);
// Verify now that the back press no longer calls the organizer
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
@@ -1012,8 +1018,8 @@
@Test
public void testBLASTCallbackWithWindows() throws Exception {
- final Task stackController = createStack();
- final Task task = createTask(stackController);
+ final Task rootTaskController = createRootTask();
+ final Task task = createTask(rootTaskController);
final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1");
final WindowState w2 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 2");
makeWindowVisible(w1);
@@ -1077,7 +1083,7 @@
final ITaskOrganizer organizer = registerMockOrganizer();
Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
- final Task task1 = createStack();
+ final Task task1 = createRootTask();
final Task task2 = createTask(rootTask, false /* fakeDraw */);
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(task1.mRemoteToken.toWindowContainerToken(),
@@ -1090,19 +1096,19 @@
@Test
public void testAppearDeferThenInfoChange() {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
+ final Task rootTask = createRootTask();
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
- final Task task = createTask(stack);
- final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task);
+ final Task task = createTask(rootTask);
+ final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_APPEARED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1112,35 +1118,35 @@
@Test
public void testAppearDeferThenVanish() {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
+ final Task rootTask = createRootTask();
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
- final Task task = createTask(stack);
+ final Task task = createTask(rootTask);
- stack.removeImmediately();
+ rootTask.removeImmediately();
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
assertEquals(0, pendingEvents.size());
}
@Test
public void testInfoChangeDeferMultiple() {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
- final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1149,7 +1155,7 @@
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription2"));
waitUntilHandlersIdle();
- pendingEvents = getTaskPendingEvent(stack);
+ pendingEvents = getTaskPendingEvent(rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription2",
@@ -1159,20 +1165,20 @@
@Test
public void testInfoChangDeferThenVanish() {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
- final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
- stack.removeImmediately();
+ rootTask.removeImmediately();
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1182,18 +1188,18 @@
@Test
public void testVanishDeferThenInfoChange() {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
- final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
- stack.removeImmediately();
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ rootTask.removeImmediately();
+ rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
@@ -1201,19 +1207,19 @@
@Test
public void testVanishDeferThenBackOnRoot() {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task stack = createStack();
- final Task task = createTask(stack);
- final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task);
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
- stack.removeImmediately();
+ rootTask.removeImmediately();
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token,
new IRequestFinishCallback.Default());
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
@@ -1264,7 +1270,7 @@
@Test
public void testSizeCompatModeChangedOnFirstOrganizedTask() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
- final Task rootTask = createStack();
+ final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task);
final ArgumentCaptor<RunningTaskInfo> infoCaptor =
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 5b5b1da..bfbe203 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -249,21 +249,22 @@
assertFalse(appWindow.canBeImeTarget());
assertFalse(imeWindow.canBeImeTarget());
- // Simulate the window is in split screen primary stack and the current state is
- // minimized and home stack is resizable, so that we should ignore input for the stack.
+ // Simulate the window is in split screen primary root task and the current state is
+ // minimized and home root task is resizable, so that we should ignore input for the
+ // root task.
final DockedTaskDividerController controller =
mDisplayContent.getDockedDividerController();
- final Task stack = createTaskStackOnDisplay(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task rootTask = createTask(mDisplayContent,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
spyOn(appWindow);
spyOn(controller);
- spyOn(stack);
- stack.setFocusable(false);
- doReturn(stack).when(appWindow).getRootTask();
+ spyOn(rootTask);
+ rootTask.setFocusable(false);
+ doReturn(rootTask).when(appWindow).getRootTask();
// Make sure canBeImeTarget is false due to shouldIgnoreInput is true;
assertFalse(appWindow.canBeImeTarget());
- assertTrue(stack.shouldIgnoreInput());
+ assertTrue(rootTask.shouldIgnoreInput());
}
@Test
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 b210dfb..e9e0c99 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -429,35 +429,55 @@
return newTaskDisplayArea;
}
- /** Creates a {@link Task} and adds it to the specified {@link DisplayContent}. */
- Task createTaskStackOnDisplay(DisplayContent dc) {
- return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
+ /**
+ * Creates a {@link Task} with a simple {@link ActivityRecord} and adds to the given
+ * {@link TaskDisplayArea}.
+ */
+ Task createTaskWithActivity(TaskDisplayArea taskDisplayArea,
+ int windowingMode, int activityType, boolean onTop, boolean twoLevelTask) {
+ return createTask(taskDisplayArea, windowingMode, activityType,
+ onTop, true /* createActivity */, twoLevelTask);
}
- Task createTaskStackOnDisplay(int windowingMode, int activityType, DisplayContent dc) {
- return new TaskBuilder(dc.mAtmService.mTaskSupervisor)
- .setDisplay(dc)
+ /** Creates a {@link Task} and adds to the given {@link DisplayContent}. */
+ Task createTask(DisplayContent dc) {
+ return createTask(dc.getDefaultTaskDisplayArea(),
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ }
+
+ Task createTask(DisplayContent dc, int windowingMode, int activityType) {
+ return createTask(dc.getDefaultTaskDisplayArea(), windowingMode, activityType);
+ }
+
+ Task createTask(TaskDisplayArea taskDisplayArea, int windowingMode, int activityType) {
+ return createTask(taskDisplayArea, windowingMode, activityType,
+ true /* onTop */, false /* createActivity */, false /* twoLevelTask */);
+ }
+
+ /** Creates a {@link Task} and adds to the given {@link TaskDisplayArea}. */
+ Task createTask(TaskDisplayArea taskDisplayArea, int windowingMode, int activityType,
+ boolean onTop, boolean createActivity, boolean twoLevelTask) {
+ final TaskBuilder builder = new TaskBuilder(mSupervisor)
+ .setTaskDisplayArea(taskDisplayArea)
.setWindowingMode(windowingMode)
.setActivityType(activityType)
- .setIntent(new Intent())
- .build();
+ .setOnTop(onTop)
+ .setCreateActivity(createActivity);
+ if (twoLevelTask) {
+ return builder
+ .setCreateParentTask(true)
+ .build()
+ .getRootTask();
+ } else {
+ return builder.build();
+ }
}
- Task createTaskStackOnTaskDisplayArea(int windowingMode, int activityType,
- TaskDisplayArea tda) {
- return new TaskBuilder(tda.mDisplayContent.mAtmService.mTaskSupervisor)
- .setTaskDisplayArea(tda)
- .setWindowingMode(windowingMode)
- .setActivityType(activityType)
- .setIntent(new Intent())
- .build();
- }
-
- /** Creates a {@link Task} and adds it to the specified {@link Task}. */
- Task createTaskInStack(Task stack, int userId) {
- final Task task = new TaskBuilder(stack.mTaskSupervisor)
+ /** Creates a {@link Task} and adds to the given root {@link Task}. */
+ Task createTaskInRootTask(Task rootTask, int userId) {
+ final Task task = new TaskBuilder(rootTask.mTaskSupervisor)
.setUserId(userId)
- .setParentTask(stack)
+ .setParentTask(rootTask)
.build();
return task;
}
@@ -485,7 +505,7 @@
*/
ActivityRecord createActivityRecord(DisplayContent dc, int windowingMode,
int activityType) {
- final Task task = createTaskStackOnDisplay(windowingMode, activityType, dc);
+ final Task task = createTask(dc, windowingMode, activityType);
return createActivityRecord(dc, task);
}
@@ -517,7 +537,7 @@
*/
ActivityRecord createActivityRecordWithParentTask(DisplayContent dc, int windowingMode,
int activityType) {
- final Task task = createTaskStackOnDisplay(windowingMode, activityType, dc);
+ final Task task = createTask(dc, windowingMode, activityType);
return createActivityRecordWithParentTask(task);
}
@@ -871,7 +891,7 @@
.setParentTask(mParentTask).build();
} else if (mTask == null && mParentTask != null && DisplayContent.alwaysCreateRootTask(
mParentTask.getWindowingMode(), mParentTask.getActivityType())) {
- // The stack can be the task root.
+ // The parent task can be the task root.
mTask = mParentTask;
}
@@ -921,9 +941,9 @@
doReturn(true).when(activity).fillsParent();
mTask.addChild(activity);
if (mOnTop) {
- // Move the task to front after activity added.
- // Or {@link TaskDisplayArea#mPreferredTopFocusableStack} could be other stacks
- // (e.g. home stack).
+ // Move the task to front after activity is added.
+ // Or {@link TaskDisplayArea#mPreferredTopFocusableRootTask} could be other
+ // root tasks (e.g. home root task).
mTask.moveToFront("createActivity");
}
// Make visible by default...
@@ -1127,8 +1147,8 @@
.build();
if (mOnTop) {
// We move the task to front again in order to regain focus after activity
- // added to the stack. Or {@link TaskDisplayArea#mPreferredTopFocusableStack}
- // could be other stacks (e.g. home stack).
+ // is added. Or {@link TaskDisplayArea#mPreferredTopFocusableRootTask} could be
+ // other root tasks (e.g. home root task).
task.moveToFront("createActivityTask");
} else {
task.moveToBack("createActivityTask", null);
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index afa35fe..2e692e6 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -917,20 +917,31 @@
boolean prevHostConnected = mHostConnected;
UsbPort port = (UsbPort) args.arg1;
UsbPortStatus status = (UsbPortStatus) args.arg2;
- mHostConnected = status.getCurrentDataRole() == DATA_ROLE_HOST;
- mSourcePower = status.getCurrentPowerRole() == POWER_ROLE_SOURCE;
- mSinkPower = status.getCurrentPowerRole() == POWER_ROLE_SINK;
- mAudioAccessoryConnected = (status.getCurrentMode() == MODE_AUDIO_ACCESSORY);
+
+ if (status != null) {
+ mHostConnected = status.getCurrentDataRole() == DATA_ROLE_HOST;
+ mSourcePower = status.getCurrentPowerRole() == POWER_ROLE_SOURCE;
+ mSinkPower = status.getCurrentPowerRole() == POWER_ROLE_SINK;
+ mAudioAccessoryConnected = (status.getCurrentMode() == MODE_AUDIO_ACCESSORY);
+
+ // Ideally we want to see if PR_SWAP and DR_SWAP is supported.
+ // But, this should be suffice, since, all four combinations are only supported
+ // when PR_SWAP and DR_SWAP are supported.
+ mSupportsAllCombinations = status.isRoleCombinationSupported(
+ POWER_ROLE_SOURCE, DATA_ROLE_HOST)
+ && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST)
+ && status.isRoleCombinationSupported(POWER_ROLE_SOURCE,
+ DATA_ROLE_DEVICE)
+ && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
+ } else {
+ mHostConnected = false;
+ mSourcePower = false;
+ mSinkPower = false;
+ mAudioAccessoryConnected = false;
+ mSupportsAllCombinations = false;
+ }
+
mAudioAccessorySupported = port.isModeSupported(MODE_AUDIO_ACCESSORY);
- // Ideally we want to see if PR_SWAP and DR_SWAP is supported.
- // But, this should be suffice, since, all four combinations are only supported
- // when PR_SWAP and DR_SWAP are supported.
- mSupportsAllCombinations = status.isRoleCombinationSupported(
- POWER_ROLE_SOURCE, DATA_ROLE_HOST)
- && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST)
- && status.isRoleCombinationSupported(POWER_ROLE_SOURCE,
- DATA_ROLE_DEVICE)
- && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
args.recycle();
updateUsbNotification(false);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 6541774..11ccfd8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -96,10 +96,10 @@
mBound = connected;
if (connected) {
try {
- service.setConfig(options, sharedMemory);
+ service.updateState(options, sharedMemory);
} catch (RemoteException e) {
// TODO: (b/181842909) Report an error to voice interactor
- Slog.w(TAG, "Failed to setConfig for HotwordDetectionService", e);
+ Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
}
}
}
@@ -129,9 +129,9 @@
}
}
- void setConfigLocked(PersistableBundle options, SharedMemory sharedMemory) {
+ void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
mRemoteHotwordDetectionService.run(
- service -> service.setConfig(options, sharedMemory));
+ service -> service.updateState(options, sharedMemory));
}
private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 29354eb..c110b23 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -984,21 +984,19 @@
}
@Override
- public void setHotwordDetectionServiceConfig(@Nullable PersistableBundle options,
+ public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION);
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
if (mImpl == null) {
- Slog.w(TAG,
- "setHotwordDetectionServiceConfig without running voice"
- + " interaction service");
+ Slog.w(TAG, "updateState without running voice interaction service");
return;
}
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.setHotwordDetectionServiceConfigLocked(options, sharedMemory);
+ mImpl.updateStateLocked(options, sharedMemory);
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0d4c302..0742cb4 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -401,10 +401,10 @@
return mInfo.getSupportsLocalInteraction();
}
- public void setHotwordDetectionServiceConfigLocked(@Nullable PersistableBundle options,
+ public void updateStateLocked(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
if (DEBUG) {
- Slog.d(TAG, "setHotwordDetectionServiceConfigLocked");
+ Slog.d(TAG, "updateStateLocked");
}
if (mHotwordDetectionComponentName == null) {
Slog.w(TAG, "Hotword detection service name not found");
@@ -426,7 +426,7 @@
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
options, sharedMemory);
} else {
- mHotwordDetectionConnection.setConfigLocked(options, sharedMemory);
+ mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
}
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 4e64838..d77ab2b 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -687,7 +687,11 @@
public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000;
/**
- * Connection is using Cross SIM Calling.
+ * Connection is using cross sim technology.
+ * <p>
+ * Indicates that the {@link Connection} is using a cross sim technology which would
+ * register IMS over internet APN of default data subscription.
+ * <p>
*/
public static final int PROPERTY_CROSS_SIM = 0x00004000;
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
index a012498..3b28616 100644
--- a/telephony/java/android/telephony/PhysicalChannelConfig.java
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -293,6 +293,14 @@
}
/**
+ * Return a copy of this PhysicalChannelConfig object but redact all the location info.
+ * @hide
+ */
+ public PhysicalChannelConfig createLocationInfoSanitizedCopy() {
+ return new Builder(this).setPhysicalCellId(PHYSICAL_CELL_ID_UNKNOWN).build();
+ }
+
+ /**
* @return String representation of the connection status
* @hide
*/
@@ -541,6 +549,23 @@
mBand = BAND_UNKNOWN;
}
+ /**
+ * Builder object constructed from existing PhysicalChannelConfig object.
+ * @hide
+ */
+ public Builder(PhysicalChannelConfig config) {
+ mNetworkType = config.getNetworkType();
+ mFrequencyRange = config.getFrequencyRange();
+ mDownlinkChannelNumber = config.getDownlinkChannelNumber();
+ mUplinkChannelNumber = config.getUplinkChannelNumber();
+ mCellBandwidthDownlinkKhz = config.getCellBandwidthDownlinkKhz();
+ mCellBandwidthUplinkKhz = config.getCellBandwidthUplinkKhz();
+ mCellConnectionStatus = config.getConnectionStatus();
+ mContextIds = Arrays.copyOf(config.getContextIds(), config.getContextIds().length);
+ mPhysicalCellId = config.getPhysicalCellId();
+ mBand = config.getBand();
+ }
+
public PhysicalChannelConfig build() {
return new PhysicalChannelConfig(this);
}
diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java
index 2011eed..fe124ac 100644
--- a/telephony/java/android/telephony/data/NrQos.java
+++ b/telephony/java/android/telephony/data/NrQos.java
@@ -50,6 +50,18 @@
return new NrQos(in);
}
+ public int get5Qi() {
+ return fiveQi;
+ }
+
+ public int getQfi() {
+ return qosFlowId;
+ }
+
+ public int getAveragingWindow() {
+ return averagingWindowMs;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(Qos.QOS_TYPE_NR, dest, flags);
diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl
new file mode 100644
index 0000000..fd3bbb0
--- /dev/null
+++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.telephony.data;
+
+ parcelable NrQosSessionAttributes;
diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.java b/telephony/java/android/telephony/data/NrQosSessionAttributes.java
new file mode 100644
index 0000000..857ccb9
--- /dev/null
+++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.QosSessionAttributes;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides Qos attributes of an NR bearer.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class NrQosSessionAttributes implements Parcelable, QosSessionAttributes {
+ private static final String TAG = NrQosSessionAttributes.class.getSimpleName();
+ private final int m5Qi;
+ private final int mQfi;
+ private final long mMaxUplinkBitRate;
+ private final long mMaxDownlinkBitRate;
+ private final long mGuaranteedUplinkBitRate;
+ private final long mGuaranteedDownlinkBitRate;
+ private final long mAveragingWindow;
+ @NonNull private final List<InetSocketAddress> mRemoteAddresses;
+
+ /**
+ * 5G QOS Identifier (5QI), see 3GPP TS 24.501 and 23.501.
+ * The allowed values are standard values(1-9, 65-68, 69-70, 75, 79-80, 82-85)
+ * defined in the spec and operator specific values in the range 128-254.
+ *
+ * @return the 5QI of the QOS flow
+ */
+ public int get5Qi() {
+ return m5Qi;
+ }
+
+ /**
+ * QOS flow identifier of the QOS flow description in the
+ * range of 1 to 63. see 3GPP TS 24.501 and 23.501.
+ *
+ * @return the QOS flow identifier of the session
+ */
+ public int getQfi() {
+ return mQfi;
+ }
+
+ /**
+ * Minimum bit rate in kbps that is guaranteed to be provided by the network on the uplink.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the guaranteed bit rate in kbps
+ */
+ public long getGuaranteedUplinkBitRate() {
+ return mGuaranteedUplinkBitRate;
+ }
+
+ /**
+ * Minimum bit rate in kbps that is guaranteed to be provided by the network on the downlink.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the guaranteed bit rate in kbps
+ */
+ public long getGuaranteedDownlinkBitRate() {
+ return mGuaranteedDownlinkBitRate;
+ }
+
+ /**
+ * The maximum uplink kbps that the network will accept.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the max uplink bit rate in kbps
+ */
+ public long getMaxUplinkBitRate() {
+ return mMaxUplinkBitRate;
+ }
+
+ /**
+ * The maximum downlink kbps that the network can provide.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the max downlink bit rate in kbps
+ */
+ public long getMaxDownlinkBitRate() {
+ return mMaxDownlinkBitRate;
+ }
+
+ /**
+ * The duration in milliseconds over which the maximum bit rates and guaranteed bit rates
+ * are calculated
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * @return the averaging window duration in milliseconds
+ */
+ public long getAveragingWindow() {
+ return mAveragingWindow;
+ }
+
+ /**
+ * List of remote addresses associated with the Qos Session. The given uplink bit rates apply
+ * to this given list of remote addresses.
+ *
+ * Note: In the event that the list is empty, it is assumed that the uplink bit rates apply to
+ * all remote addresses that are not contained in a different set of attributes.
+ *
+ * @return list of remote socket addresses that the attributes apply to
+ */
+ @NonNull
+ public List<InetSocketAddress> getRemoteAddresses() {
+ return mRemoteAddresses;
+ }
+
+ /**
+ * ..ctor for attributes
+ *
+ * @param fiveQi 5G quality class indicator
+ * @param qfi QOS flow identifier
+ * @param maxDownlinkBitRate the max downlink bit rate in kbps
+ * @param maxUplinkBitRate the max uplink bit rate in kbps
+ * @param guaranteedDownlinkBitRate the guaranteed downlink bit rate in kbps
+ * @param guaranteedUplinkBitRate the guaranteed uplink bit rate in kbps
+ * @param averagingWindow the averaging window duration in milliseconds
+ * @param remoteAddresses the remote addresses that the uplink bit rates apply to
+ *
+ * @hide
+ */
+ public NrQosSessionAttributes(final int fiveQi, final int qfi,
+ final long maxDownlinkBitRate, final long maxUplinkBitRate,
+ final long guaranteedDownlinkBitRate, final long guaranteedUplinkBitRate,
+ final long averagingWindow, @NonNull final List<InetSocketAddress> remoteAddresses) {
+ Objects.requireNonNull(remoteAddresses, "remoteAddress must be non-null");
+ m5Qi = fiveQi;
+ mQfi = qfi;
+ mMaxDownlinkBitRate = maxDownlinkBitRate;
+ mMaxUplinkBitRate = maxUplinkBitRate;
+ mGuaranteedDownlinkBitRate = guaranteedDownlinkBitRate;
+ mGuaranteedUplinkBitRate = guaranteedUplinkBitRate;
+ mAveragingWindow = averagingWindow;
+
+ final List<InetSocketAddress> remoteAddressesTemp = copySocketAddresses(remoteAddresses);
+ mRemoteAddresses = Collections.unmodifiableList(remoteAddressesTemp);
+ }
+
+ private static List<InetSocketAddress> copySocketAddresses(
+ @NonNull final List<InetSocketAddress> remoteAddresses) {
+ final List<InetSocketAddress> remoteAddressesTemp = new ArrayList<>();
+ for (final InetSocketAddress socketAddress : remoteAddresses) {
+ if (socketAddress != null && socketAddress.getAddress() != null) {
+ remoteAddressesTemp.add(socketAddress);
+ }
+ }
+ return remoteAddressesTemp;
+ }
+
+ private NrQosSessionAttributes(@NonNull final Parcel in) {
+ m5Qi = in.readInt();
+ mQfi = in.readInt();
+ mMaxDownlinkBitRate = in.readLong();
+ mMaxUplinkBitRate = in.readLong();
+ mGuaranteedDownlinkBitRate = in.readLong();
+ mGuaranteedUplinkBitRate = in.readLong();
+ mAveragingWindow = in.readLong();
+
+ final int size = in.readInt();
+ final List<InetSocketAddress> remoteAddresses = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ final byte[] addressBytes = in.createByteArray();
+ final int port = in.readInt();
+ try {
+ remoteAddresses.add(
+ new InetSocketAddress(InetAddress.getByAddress(addressBytes), port));
+ } catch (final UnknownHostException e) {
+ // Impossible case since its filtered out the null values in the ..ctor
+ Log.e(TAG, "unable to unparcel remote address at index: " + i, e);
+ }
+ }
+ mRemoteAddresses = Collections.unmodifiableList(remoteAddresses);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+ dest.writeInt(m5Qi);
+ dest.writeInt(mQfi);
+ dest.writeLong(mMaxDownlinkBitRate);
+ dest.writeLong(mMaxUplinkBitRate);
+ dest.writeLong(mGuaranteedDownlinkBitRate);
+ dest.writeLong(mGuaranteedUplinkBitRate);
+ dest.writeLong(mAveragingWindow);
+
+ final int size = mRemoteAddresses.size();
+ dest.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ final InetSocketAddress address = mRemoteAddresses.get(i);
+ dest.writeByteArray(address.getAddress().getAddress());
+ dest.writeInt(address.getPort());
+ }
+ }
+
+ @NonNull
+ public static final Creator<NrQosSessionAttributes> CREATOR =
+ new Creator<NrQosSessionAttributes>() {
+ @NonNull
+ @Override
+ public NrQosSessionAttributes createFromParcel(@NonNull final Parcel in) {
+ return new NrQosSessionAttributes(in);
+ }
+
+ @NonNull
+ @Override
+ public NrQosSessionAttributes[] newArray(final int size) {
+ return new NrQosSessionAttributes[size];
+ }
+ };
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index b25bc99..dfb229d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -136,7 +136,7 @@
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
- @Presubmit
+ @FlakyTest
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 95b1d3c..be0357e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -153,7 +153,7 @@
testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
}
- @Presubmit
+ @FlakyTest
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index a9888b1..a3d1395 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -24,6 +25,7 @@
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -56,6 +58,12 @@
}
}
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index a19a95d..62b9b81 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -80,6 +80,24 @@
super.navBarLayerRotatesAndScales()
}
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() {
+ super.statusBarLayerRotatesScales()
+ }
+
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index dcc64c9..38af8a7d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -24,6 +25,7 @@
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -61,6 +63,18 @@
}
}
+ @FlakyTest
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 6d2a923..f037f08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -79,7 +80,7 @@
testSpec.navBarLayerIsAlwaysVisible()
}
- @Presubmit
+ @FlakyTest
@Test
open fun navBarLayerRotatesAndScales() {
testSpec.navBarLayerRotatesAndScales(
@@ -98,14 +99,14 @@
testSpec.statusBarLayerIsAlwaysVisible()
}
- @Presubmit
+ @FlakyTest
@Test
open fun statusBarLayerRotatesScales() {
testSpec.statusBarLayerRotatesScales(
testSpec.config.startRotation, testSpec.config.endRotation)
}
- @Presubmit
+ @FlakyTest
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
@@ -121,7 +122,7 @@
}
}
- @Presubmit
+ @FlakyTest
@Test
open fun noUncoveredRegions() {
testSpec.noUncoveredRegions(testSpec.config.startRotation,
@@ -134,7 +135,7 @@
testSpec.focusDoesNotChange()
}
- @Presubmit
+ @FlakyTest
@Test
open fun appLayerRotates_StartingPos() {
testSpec.assertLayersStart {
@@ -142,7 +143,7 @@
}
}
- @Presubmit
+ @FlakyTest
@Test
open fun appLayerRotates_EndingPos() {
testSpec.assertLayersEnd {
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 14dddcbd..e039ef0 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -73,7 +73,7 @@
import kotlin.test.fail
const val SERVICE_BIND_TIMEOUT_MS = 5_000L
-const val TEST_TIMEOUT_MS = 1_000L
+const val TEST_TIMEOUT_MS = 10_000L
/**
* Test that exercises an instrumented version of ConnectivityService against an instrumented
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index fd37652..44298d4 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -265,6 +265,7 @@
import android.system.Os;
import android.telephony.TelephonyManager;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -9963,7 +9964,7 @@
&& session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
- .sendQosSessionLost(qosCallbackId, sessionId);
+ .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER);
waitForIdle();
verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session ->
session.getSessionId() == sessionId
@@ -9971,6 +9972,36 @@
}
@Test
+ public void testNrQosCallbackAvailableAndLost() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ when(mQosCallbackMockHelper.mFilter.validate())
+ .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ waitForIdle();
+
+ final NrQosSessionAttributes attributes = new NrQosSessionAttributes(
+ 1, 2, 3, 4, 5, 6, 7, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ waitForIdle();
+
+ verify(mQosCallbackMockHelper.mCallback).onNrQosSessionAvailable(argThat(session ->
+ session.getSessionId() == sessionId
+ && session.getSessionType() == QosSession.TYPE_NR_BEARER), eq(attributes));
+
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_NR_BEARER);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session ->
+ session.getSessionId() == sessionId
+ && session.getSessionType() == QosSession.TYPE_NR_BEARER));
+ }
+
+ @Test
public void testQosCallbackTooManyRequests() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 97c65eb..8b072c4 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -83,10 +83,10 @@
IpConnectivityMetrics mService;
NetdEventListenerService mNetdListener;
- final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
- final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
@@ -590,7 +590,7 @@
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
- netId == 100 ? mNcWifi : mNcCell);
+ netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
}
void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 52975e4..50aaaee 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -63,10 +63,10 @@
NetdEventListenerService mService;
ConnectivityManager mCm;
- final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
- final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
@@ -475,7 +475,7 @@
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
- netId == 100 ? mNcWifi : mNcCell);
+ netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
}
Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index babea36..f15d420 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -49,7 +49,9 @@
import android.annotation.NonNull;
import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
@@ -336,6 +338,13 @@
return captor.getValue();
}
+ private BroadcastReceiver getPackageChangeReceiver() {
+ final ArgumentCaptor<BroadcastReceiver> captor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext).registerReceiver(captor.capture(), any(), any(), any());
+ return captor.getValue();
+ }
+
private Vcn startAndGetVcnInstance(ParcelUuid uuid) {
mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
return mVcnMgmtSvc.getAllVcns().get(uuid);
@@ -412,6 +421,42 @@
}
@Test
+ public void testPackageChangeListenerRegistered() throws Exception {
+ verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> {
+ return filter.hasAction(Intent.ACTION_PACKAGE_ADDED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
+ }), any(), any());
+ }
+
+ @Test
+ public void testPackageChangeListener_packageAdded() throws Exception {
+ final BroadcastReceiver receiver = getPackageChangeReceiver();
+
+ verify(mMockContext).registerReceiver(any(), argThat(filter -> {
+ return filter.hasAction(Intent.ACTION_PACKAGE_ADDED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
+ }), any(), any());
+
+ receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_ADDED));
+ verify(mSubscriptionTracker).handleSubscriptionsChanged();
+ }
+
+ @Test
+ public void testPackageChangeListener_packageRemoved() throws Exception {
+ final BroadcastReceiver receiver = getPackageChangeReceiver();
+
+ verify(mMockContext).registerReceiver(any(), argThat(filter -> {
+ return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
+ }), any(), any());
+
+ receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED));
+ verify(mSubscriptionTracker).handleSubscriptionsChanged();
+ }
+
+ @Test
public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();